From f0c4df15abe40806565695688e3656f81ff5e4f2 Mon Sep 17 00:00:00 2001 From: Ryan Date: Thu, 26 Oct 2023 20:48:41 +0100 Subject: [PATCH] * Not releasable in any way * Introduce runtime / node * Feature split common for diesel / graphql and remove wasm incompatible dependencies * Common now no_std * Unfortunate introduction of a second iri crate, we need to address the ld dependency properly --- Cargo.lock | 9065 +++++++++++++---- Cargo.toml | 16 +- LICENSE | 217 +- README.md | 187 +- crates/api/Cargo.toml | 2 - crates/api/src/chronicle_graphql/activity.rs | 273 +- crates/api/src/chronicle_graphql/agent.rs | 167 +- .../src/chronicle_graphql/authorization.rs | 350 +- .../api/src/chronicle_graphql/cursor_query.rs | 126 +- crates/api/src/chronicle_graphql/entity.rs | 193 +- crates/api/src/chronicle_graphql/mod.rs | 1708 ++-- crates/api/src/chronicle_graphql/mutation.rs | 586 +- crates/api/src/chronicle_graphql/query.rs | 610 +- crates/api/src/commands.rs | 315 + crates/api/src/import.rs | 60 + crates/api/src/inmem.rs | 609 +- crates/api/src/lib.rs | 4157 ++++---- crates/api/src/opa.rs | 157 + crates/api/src/persistence/database.rs | 65 + crates/api/src/persistence/mod.rs | 2702 +++-- crates/api/src/persistence/query.rs | 90 +- crates/api/src/persistence/schema.rs | 214 +- crates/chronicle-domain-lint/build.rs | 10 +- crates/chronicle-domain-lint/src/main.rs | 32 +- crates/chronicle-domain-test/build.rs | 16 +- crates/chronicle-domain-test/src/test.rs | 1562 ++- crates/chronicle-domain/build.rs | 30 +- crates/chronicle-protocol/build.rs | 12 +- crates/chronicle-protocol/src/address.rs | 44 +- crates/chronicle-protocol/src/lib.rs | 14 +- crates/chronicle-protocol/src/messages.rs | 215 +- crates/chronicle-protocol/src/protocol.rs | 457 +- crates/chronicle-protocol/src/settings.rs | 108 +- .../src/embedded_secret_manager_source.rs | 88 +- crates/chronicle-signing/src/error.rs | 19 + crates/chronicle-signing/src/lib.rs | 912 +- crates/chronicle-signing/src/types.rs | 203 + .../src/vault_secret_manager_source.rs | 182 +- crates/chronicle-synth/build.rs | 10 +- crates/chronicle-synth/src/collection.rs | 323 +- crates/chronicle-synth/src/domain.rs | 559 +- crates/chronicle-synth/src/error.rs | 16 +- crates/chronicle-synth/src/generate.rs | 255 +- crates/chronicle-telemetry/src/telemetry.rs | 146 +- crates/chronicle/src/bootstrap/cli.rs | 1192 ++- crates/chronicle/src/bootstrap/mod.rs | 1770 ++-- crates/chronicle/src/bootstrap/opa.rs | 608 +- crates/chronicle/src/codegen/linter.rs | 253 +- crates/chronicle/src/codegen/mod.rs | 3127 +++--- crates/chronicle/src/codegen/model.rs | 1389 ++- crates/common/Cargo.toml | 92 +- crates/common/benches/opa_executor.rs | 82 - crates/common/build.rs | 29 +- crates/common/src/attributes.rs | 141 +- crates/common/src/commands.rs | 350 - crates/common/src/context.rs | 226 +- crates/common/src/database.rs | 72 - crates/common/src/identity.rs | 606 +- crates/common/src/import.rs | 60 - crates/common/src/ledger.rs | 1114 +- crates/common/src/lib.rs | 7 +- crates/common/src/opa.rs | 674 -- crates/common/src/prov/id/diesel_bindings.rs | 47 + crates/common/src/prov/id/graphlql_scalars.rs | 96 +- crates/common/src/prov/id/mod.rs | 1515 +-- crates/common/src/prov/model/contradiction.rs | 225 +- crates/common/src/prov/model/from_json_ld.rs | 891 -- .../src/prov/model/json_ld/from_json_ld.rs | 812 ++ crates/common/src/prov/model/json_ld/mod.rs | 187 + .../src/prov/model/json_ld/to_json_ld.rs | 971 ++ crates/common/src/prov/model/mod.rs | 2221 ++-- crates/common/src/prov/model/proptest.rs | 1029 +- crates/common/src/prov/model/to_json_ld.rs | 1056 -- .../common/src/prov/model/transaction/mod.rs | 68 +- .../src/prov/model/transaction/v1/mod.rs | 14 +- .../src/prov/model/transaction/v2/mod.rs | 14 +- crates/common/src/prov/operations.rs | 531 +- crates/common/src/prov/vocab.rs | 750 +- crates/frame-chronicle/Cargo.toml | 38 - crates/frame-chronicle/src/lib.rs | 186 - crates/frame-chronicle/src/tests.rs | 46 - crates/gq-subscribe/src/main.rs | 221 +- crates/id-provider/src/main.rs | 94 +- crates/opa-tp-protocol/build.rs | 26 +- crates/opa-tp-protocol/src/address.rs | 18 +- crates/opa-tp-protocol/src/events.rs | 74 +- crates/opa-tp-protocol/src/lib.rs | 14 +- crates/opa-tp-protocol/src/state.rs | 60 +- crates/opa-tp-protocol/src/submission.rs | 345 +- crates/opa-tp-protocol/src/transaction.rs | 285 +- crates/opa-tp/build.rs | 10 +- crates/opa-tp/src/abstract_tp.rs | 14 +- crates/opa-tp/src/main.rs | 122 +- crates/opa-tp/src/tp.rs | 1578 ++- crates/opactl/build.rs | 10 +- crates/opactl/src/cli.rs | 606 +- crates/opactl/src/main.rs | 1819 ++-- crates/pallet-chronicle/Cargo.toml | 42 + crates/pallet-chronicle/src/lib.rs | 185 + .../src/mock.rs | 0 crates/pallet-chronicle/src/tests.rs | 43 + .../src/weights.rs | 0 crates/pallet-chronicle/tree.txt | 1257 +++ crates/sawtooth-tp/build.rs | 10 +- crates/sawtooth-tp/src/abstract_tp.rs | 150 +- crates/sawtooth-tp/src/main.rs | 148 +- crates/sawtooth-tp/src/opa.rs | 244 +- crates/sawtooth-tp/src/tp.rs | 1264 ++- node/Containerfile | 31 + node/LICENSE | 16 + node/README.md | 164 + node/flake.lock | 43 + node/flake.nix | 22 + node/node-chronicle/Cargo.toml | 80 + node/node-chronicle/build.rs | 7 + node/node-chronicle/src/benchmarking.rs | 161 + node/node-chronicle/src/chain_spec.rs | 158 + node/node-chronicle/src/cli.rs | 54 + node/node-chronicle/src/command.rs | 212 + node/node-chronicle/src/lib.rs | 3 + node/node-chronicle/src/main.rs | 14 + node/node-chronicle/src/rpc.rs | 57 + node/node-chronicle/src/service.rs | 335 + node/node-chronicle/tree.txt | 3436 +++++++ node/runtime-chronicle/Cargo.toml | 114 + node/runtime-chronicle/build.rs | 10 + node/runtime-chronicle/src/lib.rs | 573 ++ node/rust-toolchain.toml | 14 + node/rustfmt.toml | 23 + node/scripts/init.sh | 12 + node/shell.nix | 35 + rustfmt.toml | 26 +- 132 files changed, 37329 insertions(+), 26481 deletions(-) create mode 100644 crates/api/src/commands.rs create mode 100644 crates/api/src/import.rs create mode 100644 crates/api/src/opa.rs create mode 100644 crates/api/src/persistence/database.rs create mode 100644 crates/chronicle-signing/src/error.rs create mode 100644 crates/chronicle-signing/src/types.rs delete mode 100644 crates/common/benches/opa_executor.rs delete mode 100644 crates/common/src/commands.rs delete mode 100644 crates/common/src/database.rs delete mode 100644 crates/common/src/import.rs delete mode 100644 crates/common/src/opa.rs create mode 100644 crates/common/src/prov/id/diesel_bindings.rs delete mode 100644 crates/common/src/prov/model/from_json_ld.rs create mode 100644 crates/common/src/prov/model/json_ld/from_json_ld.rs create mode 100644 crates/common/src/prov/model/json_ld/mod.rs create mode 100644 crates/common/src/prov/model/json_ld/to_json_ld.rs delete mode 100644 crates/common/src/prov/model/to_json_ld.rs delete mode 100644 crates/frame-chronicle/Cargo.toml delete mode 100644 crates/frame-chronicle/src/lib.rs delete mode 100644 crates/frame-chronicle/src/tests.rs create mode 100644 crates/pallet-chronicle/Cargo.toml create mode 100644 crates/pallet-chronicle/src/lib.rs rename crates/{frame-chronicle => pallet-chronicle}/src/mock.rs (100%) create mode 100644 crates/pallet-chronicle/src/tests.rs rename crates/{frame-chronicle => pallet-chronicle}/src/weights.rs (100%) create mode 100644 crates/pallet-chronicle/tree.txt create mode 100644 node/Containerfile create mode 100644 node/LICENSE create mode 100644 node/README.md create mode 100644 node/flake.lock create mode 100644 node/flake.nix create mode 100644 node/node-chronicle/Cargo.toml create mode 100644 node/node-chronicle/build.rs create mode 100644 node/node-chronicle/src/benchmarking.rs create mode 100644 node/node-chronicle/src/chain_spec.rs create mode 100644 node/node-chronicle/src/cli.rs create mode 100644 node/node-chronicle/src/command.rs create mode 100644 node/node-chronicle/src/lib.rs create mode 100644 node/node-chronicle/src/main.rs create mode 100644 node/node-chronicle/src/rpc.rs create mode 100644 node/node-chronicle/src/service.rs create mode 100644 node/node-chronicle/tree.txt create mode 100644 node/runtime-chronicle/Cargo.toml create mode 100644 node/runtime-chronicle/build.rs create mode 100644 node/runtime-chronicle/src/lib.rs create mode 100644 node/rust-toolchain.toml create mode 100644 node/rustfmt.toml create mode 100755 node/scripts/init.sh create mode 100644 node/shell.nix diff --git a/Cargo.lock b/Cargo.lock index b56644633..99791d30a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,11 +47,122 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", +] + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array 0.14.7", +] + +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher 0.2.5", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead 0.4.3", + "aes 0.7.5", + "cipher 0.3.0", + "ctr 0.8.0", + "ghash 0.4.4", + "subtle", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead 0.5.2", + "aes 0.8.3", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.0", + "subtle", +] + +[[package]] +name = "aes-soft" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +dependencies = [ + "cipher 0.2.5", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher 0.2.5", + "opaque-debug 0.3.0", +] + [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ "getrandom 0.2.10", "once_cell", @@ -60,22 +171,23 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "getrandom 0.2.10", "once_cell", "serde", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -174,7 +286,7 @@ dependencies = [ "async-stl-client", "async-stream", "async-trait", - "base64 0.21.4", + "base64 0.21.5", "cached", "cfg-if", "chronicle-protocol", @@ -191,7 +303,6 @@ dependencies = [ "hex", "insta", "iref", - "iref-enum", "json-ld", "jwtk", "lazy_static", @@ -200,7 +311,7 @@ dependencies = [ "opa", "opa-tp-protocol", "opentelemetry 0.19.0", - "parking_lot", + "parking_lot 0.12.1", "poem", "portpicker", "prost 0.10.4", @@ -214,7 +325,6 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "static-iref", "tempfile", "thiserror", "tmq", @@ -223,7 +333,7 @@ dependencies = [ "tracing", "url", "user-error", - "uuid 1.4.1", + "uuid 1.5.0", ] [[package]] @@ -251,9 +361,15 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.3.0" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "arc-swap" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "array-bytes" @@ -285,6 +401,73 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" +[[package]] +name = "asn1-rs" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33" +dependencies = [ + "asn1-rs-derive 0.1.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive 0.4.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -304,7 +487,7 @@ dependencies = [ "anstyle", "doc-comment", "globwalk", - "predicates", + "predicates 3.0.4", "predicates-core", "predicates-tree", "tempfile", @@ -347,7 +530,7 @@ dependencies = [ "num-traits", "once_cell", "opentelemetry 0.19.0", - "pin-project-lite", + "pin-project-lite 0.2.13", "regex", "serde", "serde_json", @@ -355,7 +538,7 @@ dependencies = [ "static_assertions", "tempfile", "thiserror", - "uuid 1.4.1", + "uuid 1.5.0", ] [[package]] @@ -411,6 +594,26 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + [[package]] name = "async-lock" version = "2.8.0" @@ -420,6 +623,17 @@ dependencies = [ "event-listener", ] +[[package]] +name = "async-recursion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "async-stl-client" version = "0.7.5" @@ -434,11 +648,11 @@ dependencies = [ "k256 0.11.6", "lazy_static", "lru", - "pin-project-lite", + "pin-project-lite 0.2.13", "pinvec", "pow_of_2", "prost 0.10.4", - "prost-build", + "prost-build 0.10.4", "rand 0.8.5", "rand_core 0.6.4", "serde", @@ -448,7 +662,7 @@ dependencies = [ "tokio", "tokio-stream", "tracing", - "uuid 1.4.1", + "uuid 1.5.0", "zmq", ] @@ -460,7 +674,7 @@ checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", - "pin-project-lite", + "pin-project-lite 0.2.13", ] [[package]] @@ -471,24 +685,24 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "async-task" -version = "4.4.1" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9441c6b2fe128a7c2bf680a44c34d0df31ce09e5b7e401fcca3faa483dbc921" +checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -497,6 +711,19 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82" +[[package]] +name = "asynchronous-codec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite 0.2.13", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -539,7 +766,7 @@ dependencies = [ "memchr", "mime", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.13", "rustversion", "serde", "sync_wrapper", @@ -580,6 +807,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + [[package]] name = "base16ct" version = "0.1.1" @@ -600,9 +833,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -610,6 +843,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +dependencies = [ + "serde", +] + [[package]] name = "bincode" version = "1.3.3" @@ -619,6 +861,27 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "prettyplease 0.2.15", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.38", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -642,9 +905,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bitvec" @@ -678,13 +941,37 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "blake2s_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" +dependencies = [ + "arrayref", + "arrayvec 0.7.4", + "constant_time_eq", +] + +[[package]] +name = "blake3" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +dependencies = [ + "arrayref", + "arrayvec 0.7.4", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "block-padding", + "block-padding 0.1.5", "byte-tools", "byteorder", "generic-array 0.12.4", @@ -708,6 +995,16 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "block-modes" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" +dependencies = [ + "block-padding 0.2.1", + "cipher 0.2.5", +] + [[package]] name = "block-padding" version = "0.1.5" @@ -717,16 +1014,22 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "blocking" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94c4ef1f913d78636d78d538eec1f18de81e481f44b1be0a81060090530846e1" +checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a" dependencies = [ "async-channel", "async-lock", "async-task", - "fastrand", + "fastrand 2.0.1", "futures-io", "futures-lite", "piper", @@ -746,9 +1049,9 @@ dependencies = [ [[package]] name = "bounded-collections" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b05133427c07c4776906f673ccf36c21b102c9829c641a5b56bd151d44fd6" +checksum = "ca548b6163b872067dc5eb82fd130c56881435e30367d2073594a3d9744120dd" dependencies = [ "log", "parity-scale-codec", @@ -756,6 +1059,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + [[package]] name = "bs58" version = "0.5.0" @@ -767,14 +1076,23 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" dependencies = [ "memchr", "serde", ] +[[package]] +name = "build-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdce191bf3fa4995ce948c8c83b4640a1745457a149e73c6db75b4ffe36aad5f" +dependencies = [ + "semver 0.6.0", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -795,9 +1113,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytecount" -version = "0.6.4" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "bytemuck" @@ -807,9 +1125,9 @@ checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -820,6 +1138,17 @@ dependencies = [ "serde", ] +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cached" version = "0.42.0" @@ -858,6 +1187,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + [[package]] name = "cargo-husky" version = "1.5.0" @@ -865,20 +1203,64 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b02b629252fe8ef6460461409564e2c21d0c8e77e0944f3d189ff06c4e932ad" [[package]] -name = "cast" -version = "0.3.0" +name = "cargo-platform" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" +dependencies = [ + "serde", +] [[package]] -name = "cc" +name = "cargo_metadata" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.20", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] +[[package]] +name = "ccm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca1a8fbc20b50ac9673ff014abfb2b5f4085ee1a850d408f14a159c5853ac7" +dependencies = [ + "aead 0.3.2", + "cipher 0.2.5", + "subtle", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-expr" version = "0.15.5" @@ -894,6 +1276,37 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead 0.4.3", + "chacha20", + "cipher 0.3.0", + "poly1305", + "zeroize", +] + [[package]] name = "chronicle" version = "0.7.5" @@ -943,7 +1356,7 @@ dependencies = [ "tracing-log", "url", "user-error", - "uuid 1.4.1", + "uuid 1.5.0", "valico", ] @@ -979,7 +1392,7 @@ dependencies = [ "tempfile", "tracing", "tracing-log", - "uuid 1.4.1", + "uuid 1.5.0", ] [[package]] @@ -1002,7 +1415,7 @@ dependencies = [ "opa-tp-protocol", "openssl", "prost 0.10.4", - "prost-build", + "prost-build 0.10.4", "prost-types 0.11.9", "rand 0.8.5", "rand_core 0.6.4", @@ -1014,7 +1427,7 @@ dependencies = [ "tokio", "tracing", "url", - "uuid 1.4.1", + "uuid 1.5.0", "zmq", ] @@ -1107,6 +1520,58 @@ dependencies = [ "half", ] +[[package]] +name = "cid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b68e3193982cd54187d71afdb2a271ad4cf8af157858e9cb911b91321de143" +dependencies = [ + "core2", + "multibase", + "multihash", + "serde", + "unsigned-varint", +] + +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "3.2.25" @@ -1126,23 +1591,23 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.6" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" dependencies = [ "clap_builder", - "clap_derive 4.4.2", + "clap_derive 4.4.7", ] [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" dependencies = [ "anstream", "anstyle", - "clap_lex 0.5.1", + "clap_lex 0.6.0", "strsim", ] @@ -1170,14 +1635,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1191,9 +1656,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "cmake" @@ -1204,6 +1669,16 @@ dependencies = [ "cc", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -1223,9 +1698,9 @@ dependencies = [ [[package]] name = "colored_json" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74cb9ce6b86f6e54bfa9518df2eeeef65d424ec7244d083ed97229185e366a91" +checksum = "4948aed0a773db233baf8997302da553ad285be28464c36584944a66ab107db2" dependencies = [ "is-terminal", "serde", @@ -1233,6 +1708,17 @@ dependencies = [ "yansi", ] +[[package]] +name = "comfy-table" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" +dependencies = [ + "strum 0.25.0", + "strum_macros 0.25.3", + "unicode-width", +] + [[package]] name = "common" version = "0.7.5" @@ -1240,20 +1726,16 @@ dependencies = [ "anyhow", "async-graphql", "async-trait", - "chronicle-signing", "chrono", "criterion", - "custom_error", - "derivative", "diesel", - "frame-support", "futures", "glob", "hashbrown 0.13.2", "hex", "insta", "iref", - "iref-enum", + "iri-string", "json-ld", "json-syntax", "k256 0.11.6", @@ -1263,33 +1745,21 @@ dependencies = [ "mime", "mockito", "newtype-derive-2018", - "opa", - "opa-tp-protocol", - "openssl", "parity-scale-codec", - "percent-encoding", - "pkcs8 0.10.2", "proptest", - "prost 0.10.4", - "r2d2", - "rand 0.8.5", - "rand_core 0.6.4", "rdf-types", - "reqwest", - "rust-embed", "scale-info", "serde", "serde_derive", "serde_json", - "static-iref", + "sp-std", "tempfile", "testcontainers", "thiserror", + "thiserror-no-std", "tokio", "tracing", - "url", - "uuid 1.4.1", - "zmq", + "uuid 1.5.0", ] [[package]] @@ -1316,6 +1786,7 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", + "unicode-width", "windows-sys 0.45.0", ] @@ -1363,40 +1834,38 @@ checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "const-random" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a" dependencies = [ "const-random-macro", - "proc-macro-hack", ] [[package]] name = "const-random-macro" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ "getrandom 0.2.10", "once_cell", - "proc-macro-hack", "tiny-keccak", ] [[package]] name = "const_format" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" dependencies = [ "proc-macro2", "quote", @@ -1431,6 +1900,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpp_demangle" version = "0.3.5" @@ -1442,13 +1920,22 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] +[[package]] +name = "cranelift-bforest" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1277fbfa94bc82c8ec4af2ded3e639d49ca5f7f3c7eeab2c66accd135ece4e70" +dependencies = [ + "cranelift-entity 0.95.1", +] + [[package]] name = "cranelift-bforest" version = "0.97.2" @@ -1458,6 +1945,26 @@ dependencies = [ "cranelift-entity 0.97.2", ] +[[package]] +name = "cranelift-codegen" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e8c31ad3b2270e9aeec38723888fe1b0ace3bea2b06b3f749ccf46661d3220" +dependencies = [ + "bumpalo", + "cranelift-bforest 0.95.1", + "cranelift-codegen-meta 0.95.1", + "cranelift-codegen-shared 0.95.1", + "cranelift-entity 0.95.1", + "cranelift-isle 0.95.1", + "gimli 0.27.3", + "hashbrown 0.13.2", + "log", + "regalloc2 0.6.1", + "smallvec", + "target-lexicon", +] + [[package]] name = "cranelift-codegen" version = "0.97.2" @@ -1465,29 +1972,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95551de96900cefae691ce895ff2abc691ae3a0b97911a76b45faf99e432937b" dependencies = [ "bumpalo", - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", + "cranelift-bforest 0.97.2", + "cranelift-codegen-meta 0.97.2", + "cranelift-codegen-shared 0.97.2", "cranelift-control", "cranelift-entity 0.97.2", - "cranelift-isle", + "cranelift-isle 0.97.2", "gimli 0.27.3", "hashbrown 0.13.2", "log", - "regalloc2", + "regalloc2 0.9.3", "smallvec", "target-lexicon", ] +[[package]] +name = "cranelift-codegen-meta" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ac5ac30d62b2d66f12651f6b606dbdfd9c2cfd0908de6b387560a277c5c9da" +dependencies = [ + "cranelift-codegen-shared 0.95.1", +] + [[package]] name = "cranelift-codegen-meta" version = "0.97.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36a3ad7b2bb03de3383f258b00ca29d80234bebd5130cb6ef3bae37ada5baab0" dependencies = [ - "cranelift-codegen-shared", + "cranelift-codegen-shared 0.97.2", ] +[[package]] +name = "cranelift-codegen-shared" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd82b8b376247834b59ed9bdc0ddeb50f517452827d4a11bccf5937b213748b8" + [[package]] name = "cranelift-codegen-shared" version = "0.97.2" @@ -1521,44 +2043,89 @@ dependencies = [ "serde", ] +[[package]] +name = "cranelift-frontend" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a25d9d0a0ae3079c463c34115ec59507b4707175454f0eee0891e83e30e82d" +dependencies = [ + "cranelift-codegen 0.95.1", + "log", + "smallvec", + "target-lexicon", +] + [[package]] name = "cranelift-frontend" version = "0.97.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bffa38431f7554aa1594f122263b87c9e04abc55c9f42b81d37342ac44f79f0" dependencies = [ - "cranelift-codegen", + "cranelift-codegen 0.97.2", "log", "smallvec", "target-lexicon", ] +[[package]] +name = "cranelift-isle" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80de6a7d0486e4acbd5f9f87ec49912bf4c8fb6aea00087b989685460d4469ba" + [[package]] name = "cranelift-isle" version = "0.97.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84cef66a71c77938148b72bf006892c89d6be9274a08f7e669ff15a56145d701" +[[package]] +name = "cranelift-native" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6b03e0e03801c4b3fd8ce0758a94750c07a44e7944cc0ffbf0d3f2e7c79b00" +dependencies = [ + "cranelift-codegen 0.95.1", + "libc", + "target-lexicon", +] + [[package]] name = "cranelift-native" version = "0.97.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f33c7e5eb446e162d2d10b17fe68e1f091020cc2e4e38b5501c21099600b0a1b" dependencies = [ - "cranelift-codegen", + "cranelift-codegen 0.97.2", "libc", "target-lexicon", ] +[[package]] +name = "cranelift-wasm" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff3220489a3d928ad91e59dd7aeaa8b3de18afb554a6211213673a71c90737ac" +dependencies = [ + "cranelift-codegen 0.95.1", + "cranelift-entity 0.95.1", + "cranelift-frontend 0.95.1", + "itertools 0.10.5", + "log", + "smallvec", + "wasmparser 0.102.0", + "wasmtime-types 8.0.1", +] + [[package]] name = "cranelift-wasm" version = "0.97.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "632f7b64fa6a8c5b980eb6a17ef22089e15cb9f779f1ed3bd3072beab0686c09" dependencies = [ - "cranelift-codegen", + "cranelift-codegen 0.97.2", "cranelift-entity 0.97.2", - "cranelift-frontend", + "cranelift-frontend 0.97.2", "itertools 0.10.5", "log", "smallvec", @@ -1566,6 +2133,21 @@ dependencies = [ "wasmtime-types 10.0.2", ] +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + [[package]] name = "crc32fast" version = "1.3.2" @@ -1584,7 +2166,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.4.6", + "clap 4.4.7", "criterion-plot", "futures", "is-terminal", @@ -1693,6 +2275,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.7", + "rand_core 0.6.4", "typenum", ] @@ -1716,13 +2299,31 @@ dependencies = [ "subtle", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher 0.3.0", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "ctrlc" version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" dependencies = [ - "nix", + "nix 0.27.1", "windows-sys 0.48.0", ] @@ -1766,17 +2367,18 @@ dependencies = [ "platforms", "rustc_version", "subtle", + "zeroize", ] [[package]] name = "curve25519-dalek-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1785,6 +2387,50 @@ version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f8a51dd197fa6ba5b4dc98a990a43cc13693c23eb0089ebb0fcc1f04152bca6" +[[package]] +name = "cxx" +version = "1.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7129e341034ecb940c9072817cd9007974ea696844fc4dd582dc1653a7fbe2e8" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a24f3f5f8eed71936f21e570436f024f5c2e25628f7496aa7ccd03b90109d5" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.38", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06fdd177fc61050d63f67f5bd6351fac6ab5526694ea8e359cd9cd3b75857f44" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "587663dd5fb3d10932c8aecfe7c844db1bcf0aee93eeab08fac13dc1212c2e7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "darling" version = "0.13.4" @@ -1862,19 +2508,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] -name = "debugid" -version = "0.8.0" +name = "data-encoding-macro" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" dependencies = [ - "uuid 1.4.1", + "data-encoding", + "data-encoding-macro-internal", ] [[package]] -name = "decoded-char" -version = "0.1.1" +name = "data-encoding-macro-internal" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5440d1dc8ea7cae44cda3c64568db29bfa2434aba51ae66a50c00488841a65a3" +checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +dependencies = [ + "data-encoding", + "syn 1.0.109", +] + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid 1.5.0", +] + +[[package]] +name = "decoded-char" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5440d1dc8ea7cae44cda3c64568db29bfa2434aba51ae66a50c00488841a65a3" [[package]] name = "der" @@ -1897,11 +2563,42 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82" +dependencies = [ + "asn1-rs 0.3.1", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs 0.5.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] [[package]] name = "derivative" @@ -1925,13 +2622,34 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_builder" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" +dependencies = [ + "derive_builder_macro 0.11.2", +] + [[package]] name = "derive_builder" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" dependencies = [ - "derive_builder_macro", + "derive_builder_macro 0.12.0", +] + +[[package]] +name = "derive_builder_core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -1946,13 +2664,23 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_builder_macro" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" +dependencies = [ + "derive_builder_core 0.11.2", + "syn 1.0.109", +] + [[package]] name = "derive_builder_macro" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" dependencies = [ - "derive_builder_core", + "derive_builder_core 0.12.0", "syn 1.0.109", ] @@ -1973,14 +2701,14 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2268a214a6f118fce1838edba3d1561cf0e78d8de785475957a580a7f8c69d33" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "byteorder", "chrono", "diesel_derives", "itoa", "pq-sys", "r2d2", - "uuid 1.4.1", + "uuid 1.5.0", ] [[package]] @@ -1992,7 +2720,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2012,7 +2740,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2051,6 +2779,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs" version = "5.0.1" @@ -2072,6 +2819,28 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -2080,18 +2849,18 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "docify" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee528c501ddd15d5181997e9518e59024844eac44fd1e40cb20ddb2a8562fa" +checksum = "4235e9b248e2ba4b92007fe9c646f3adf0ffde16dc74713eacc92b8bc58d8d2f" dependencies = [ "docify_macros", ] [[package]] name = "docify_macros" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca01728ab2679c464242eca99f94e2ce0514b52ac9ad950e2ed03fca991231c" +checksum = "47020e12d7c7505670d1363dd53d6c23724f71a90a3ae32ff8eba40de8404626" dependencies = [ "common-path", "derive-syn-parse", @@ -2099,7 +2868,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.37", + "syn 2.0.38", "termcolor", "toml 0.7.8", "walkdir", @@ -2111,6 +2880,18 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "dtoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" + [[package]] name = "dyn-clonable" version = "0.9.0" @@ -2167,10 +2948,11 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ + "pkcs8 0.10.2", "signature 2.1.0", ] @@ -2182,7 +2964,10 @@ checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek 4.1.1", "ed25519", + "rand_core 0.6.4", + "serde", "sha2 0.10.8", + "zeroize", ] [[package]] @@ -2218,6 +3003,7 @@ dependencies = [ "ff 0.12.1", "generic-array 0.14.7", "group 0.12.1", + "hkdf", "pem-rfc7468", "pkcs8 0.9.0", "rand_core 0.6.4", @@ -2261,6 +3047,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "environmental" version = "1.1.4" @@ -2275,25 +3086,14 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys 0.48.0", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "error-chain" version = "0.10.0" @@ -2306,6 +3106,15 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "exit-future" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e43f2f1833d64e33f15592464d6fdd70f349dda7b1a53088eb83cd94014008c5" +dependencies = [ + "futures", +] + [[package]] name = "expander" version = "2.0.0" @@ -2316,7 +3125,7 @@ dependencies = [ "fs-err", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2350,12 +3159,30 @@ dependencies = [ "ascii_utils", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fdlimit" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4c9e43643f5a3be4ca5b67d26b98031ff9db6806c3440ae32e02e3ceac3f1b" +dependencies = [ + "libc", +] + [[package]] name = "ff" version = "0.12.1" @@ -2378,9 +3205,19 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.1" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a481586acf778f1b1455424c343f71124b048ffa5f4fc3f8f6ae9dc432dcb3c7" + +[[package]] +name = "file-per-thread-logger" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" +checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" +dependencies = [ + "env_logger", + "log", +] [[package]] name = "filetime" @@ -2394,6 +3231,22 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "finality-grandpa" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36530797b9bf31cd4ff126dcfee8170f86b00cfdcea3269d73133cc0415945c3" +dependencies = [ + "either", + "futures", + "futures-timer", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot 0.12.1", + "scale-info", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -2414,14 +3267,24 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", + "libz-sys", "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2443,6 +3306,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fork-tree" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d54d3a638f0279210c924f4a44e6548bf6345670f5af059a874a5006af4eca" +dependencies = [ + "parity-scale-codec", +] + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -2462,6 +3334,12 @@ dependencies = [ "num", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "frame-benchmarking" version = "24.0.0" @@ -2489,24 +3367,71 @@ dependencies = [ ] [[package]] -name = "frame-chronicle" -version = "0.7.5" +name = "frame-benchmarking-cli" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4493341076535acb0bdb591e0e97b32cfacb8515dd74e66156f199d187cec004" dependencies = [ - "chronicle-telemetry", - "common", + "Inflector", + "array-bytes", + "chrono", + "clap 4.4.7", + "comfy-table", "frame-benchmarking", "frame-support", "frame-system", - "macro-attr-2018", - "newtype-derive-2018", + "gethostname", + "handlebars", + "itertools 0.10.5", + "lazy_static", + "linked-hash-map", + "log", + "parity-scale-codec", + "rand 0.8.5", + "rand_pcg 0.3.1", + "sc-block-builder", + "sc-cli", + "sc-client-api", + "sc-client-db", + "sc-executor", + "sc-service", + "sc-sysinfo", + "serde", + "serde_json", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-database", + "sp-externalities", + "sp-inherents", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-state-machine", + "sp-storage", + "sp-trie", + "sp-wasm-interface", + "thiserror", + "thousands", +] + +[[package]] +name = "frame-executive" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da12a8c223d6991bd7f9aae542d3d7c9fadde3a81b6f16c2550b808f3b21ecd5" +dependencies = [ + "frame-support", + "frame-system", + "frame-try-runtime", + "log", "parity-scale-codec", "scale-info", "sp-core", "sp-io", "sp-runtime", "sp-std", - "tracing", - "uuid 1.4.1", + "sp-tracing", ] [[package]] @@ -2521,6 +3446,29 @@ dependencies = [ "serde", ] +[[package]] +name = "frame-remote-externalities" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b26379217d223364e6715ed12cdfdc9f368c6afcb15fd8771e387ab7b0265f" +dependencies = [ + "async-recursion", + "futures", + "indicatif", + "jsonrpsee", + "log", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "spinners", + "substrate-rpc-client", + "tokio", + "tokio-retry", +] + [[package]] name = "frame-support" version = "24.0.0" @@ -2578,7 +3526,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2591,7 +3539,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2602,7 +3550,7 @@ checksum = "4034ebf9ca7497fa3893191fe3e81adcd3d7cd1c232e60ef41ef58ea0c445ae9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -2626,11 +3574,60 @@ dependencies = [ ] [[package]] -name = "fs-err" +name = "frame-system-benchmarking" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb79e630dc8fbed5601e58c1b8d84ec3900a511f105140b5bbb6c18c476488d2" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "frame-system-rpc-runtime-api" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13ed2be7e4ad2cf140d16b94194595d3b2fea0b60a46832945c497924c2d0d0" +dependencies = [ + "parity-scale-codec", + "sp-api", +] + +[[package]] +name = "frame-try-runtime" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9eceb53c4efa82dd7dd08f0770abfaa9587c592a015b21dc29ce4c24422de13" +dependencies = [ + "frame-support", + "parity-scale-codec", + "sp-api", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "fs-err" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "funty" version = "2.0.0" @@ -2639,9 +3636,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -2654,9 +3651,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -2664,15 +3661,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -2682,9 +3679,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-lite" @@ -2692,38 +3689,60 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ + "fastrand 1.9.0", "futures-core", - "pin-project-lite", + "futures-io", + "memchr", + "parking", + "pin-project-lite 0.2.13", + "waker-fn", ] [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", +] + +[[package]] +name = "futures-rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" +dependencies = [ + "futures-io", + "rustls 0.20.9", + "webpki 0.22.4", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-timer" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -2732,7 +3751,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.13", "pin-utils", "slab", ] @@ -2752,7 +3771,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "debugid", "fxhash", "serde", @@ -2803,9 +3822,19 @@ dependencies = [ [[package]] name = "generics" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf88d375dffa56ba9e04298c989d0145b8fe216d2a44484b3f6294ab93381a2" +checksum = "c39102f6bbbd8e1cf7ef8f9d86b3d388cb86d0cd4781b0e31d2622bd0fcd1aa9" + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] [[package]] name = "getrandom" @@ -2831,6 +3860,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug 0.3.0", + "polyval 0.5.3", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug 0.3.0", + "polyval 0.6.1", +] + [[package]] name = "gimli" version = "0.27.3" @@ -2972,7 +4021,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", ] [[package]] @@ -2981,14 +4030,14 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", ] [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "hdrhistogram" @@ -3009,7 +4058,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "headers-core", "http", @@ -3054,6 +4103,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac 0.12.1", +] + [[package]] name = "hmac" version = "0.8.1" @@ -3103,6 +4161,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.2.9" @@ -3122,9 +4191,15 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite", + "pin-project-lite 0.2.13", ] +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "httparse" version = "1.8.0" @@ -3159,8 +4234,8 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite", - "socket2 0.4.9", + "pin-project-lite 0.2.13", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -3169,16 +4244,19 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", "hyper", - "rustls", + "log", + "rustls 0.21.8", + "rustls-native-certs", "tokio", "tokio-rustls", + "webpki-roots 0.25.2", ] [[package]] @@ -3188,7 +4266,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper", - "pin-project-lite", + "pin-project-lite 0.2.13", "tokio", "tokio-io-timeout", ] @@ -3208,16 +4286,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -3235,6 +4313,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.4.0" @@ -3245,6 +4334,35 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if-addrs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "if-watch" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb892e5777fe09e16f3d44de7802f4daa7267ecbe8c466f19d94e25bb0c303e" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "rtnetlink", + "system-configuration", + "tokio", + "windows", +] + [[package]] name = "ignore" version = "0.4.20" @@ -3328,14 +4446,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.2", +] + +[[package]] +name = "indicatif" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array 0.14.7", ] [[package]] name = "insta" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa511b2e298cd49b1856746f6bb73e17036bcd66b25f5e92cdcdbec9bd75686" +checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc" dependencies = [ "console", "lazy_static", @@ -3366,6 +4506,25 @@ dependencies = [ "num-traits", ] +[[package]] +name = "interceptor" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8a11ae2da61704edada656798b61c94b35ecac2c58eb955156987d5e6be90b" +dependencies = [ + "async-trait", + "bytes", + "log", + "rand 0.8.5", + "rtcp", + "rtp", + "thiserror", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -3377,11 +4536,29 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ip_network" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.5", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "iref" @@ -3394,15 +4571,13 @@ dependencies = [ ] [[package]] -name = "iref-enum" -version = "2.1.0" +name = "iri-string" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3920573c192ff95151ccfc69c2c48ae016149f7adeb87d76bc91297ebc8bd42" +checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0" dependencies = [ - "iref", - "proc-macro2", - "quote", - "syn 1.0.109", + "memchr", + "serde", ] [[package]] @@ -3412,7 +4587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.3", - "rustix 0.38.17", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -3449,6 +4624,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -3630,70 +4814,214 @@ dependencies = [ ] [[package]] -name = "jsonschema" -version = "0.17.1" +name = "jsonrpsee" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a071f4f7efc9a9118dfb627a0a94ef247986e1ab8606a4c806ae2b3aa3b6978" +checksum = "367a292944c07385839818bb71c8d76611138e2dedb0677d035b8da21d29c78b" dependencies = [ - "ahash 0.8.3", - "anyhow", - "base64 0.21.4", - "bytecount", - "clap 4.4.6", - "fancy-regex", - "fraction", - "getrandom 0.2.10", - "iso8601", - "itoa", - "memchr", - "num-cmp", - "once_cell", - "parking_lot", - "percent-encoding", - "regex", - "reqwest", - "serde", - "serde_json", - "time", - "url", - "uuid 1.4.1", + "jsonrpsee-core", + "jsonrpsee-http-client", + "jsonrpsee-proc-macros", + "jsonrpsee-server", + "jsonrpsee-types", + "jsonrpsee-ws-client", + "tracing", ] [[package]] -name = "jsonway" -version = "2.0.0" +name = "jsonrpsee-client-transport" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effcb749443c905fbaef49d214f8b1049c240e0adb7af9baa0e201e625e4f9de" +checksum = "c8b3815d9f5d5de348e5f162b316dc9cdf4548305ebb15b4eb9328e66cf27d7a" dependencies = [ - "serde", - "serde_json", + "futures-util", + "http", + "jsonrpsee-core", + "jsonrpsee-types", + "pin-project", + "rustls-native-certs", + "soketto", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", + "webpki-roots 0.25.2", ] [[package]] -name = "jwtk" -version = "0.2.4" +name = "jsonrpsee-core" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6833c8be7e70530a018d6c48ef2a338b4d9df198ddb9d4ec0da436820a094526" +checksum = "2b5dde66c53d6dcdc8caea1874a45632ec0fcf5b437789f1e45766a1512ce803" dependencies = [ - "base64 0.13.1", - "foreign-types", - "openssl", - "openssl-sys", - "reqwest", + "anyhow", + "arrayvec 0.7.4", + "async-lock", + "async-trait", + "beef", + "futures-channel", + "futures-timer", + "futures-util", + "globset", + "hyper", + "jsonrpsee-types", + "parking_lot 0.12.1", + "rand 0.8.5", + "rustc-hash", "serde", "serde_json", - "smallvec", + "soketto", + "thiserror", "tokio", + "tracing", ] [[package]] -name = "k256" -version = "0.11.6" +name = "jsonrpsee-http-client" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +checksum = "7e5f9fabdd5d79344728521bb65e3106b49ec405a78b66fbff073b72b389fa43" dependencies = [ - "cfg-if", - "ecdsa 0.14.8", + "async-trait", + "hyper", + "hyper-rustls", + "jsonrpsee-core", + "jsonrpsee-types", + "rustc-hash", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44e8ab85614a08792b9bff6c8feee23be78c98d0182d4c622c05256ab553892a" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4d945a6008c9b03db3354fb3c83ee02d2faa9f2e755ec1dfb69c3551b8f4ba" +dependencies = [ + "futures-channel", + "futures-util", + "http", + "hyper", + "jsonrpsee-core", + "jsonrpsee-types", + "serde", + "serde_json", + "soketto", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245ba8e5aa633dd1c1e4fae72bce06e71f42d34c14a2767c6b4d173b57bee5e5" +dependencies = [ + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "jsonrpsee-ws-client" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e1b3975ed5d73f456478681a417128597acd6a2487855fdb7b4a3d4d195bf5e" +dependencies = [ + "http", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", +] + +[[package]] +name = "jsonschema" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a071f4f7efc9a9118dfb627a0a94ef247986e1ab8606a4c806ae2b3aa3b6978" +dependencies = [ + "ahash 0.8.6", + "anyhow", + "base64 0.21.5", + "bytecount", + "clap 4.4.7", + "fancy-regex", + "fraction", + "getrandom 0.2.10", + "iso8601", + "itoa", + "memchr", + "num-cmp", + "once_cell", + "parking_lot 0.12.1", + "percent-encoding", + "regex", + "reqwest", + "serde", + "serde_json", + "time", + "url", + "uuid 1.5.0", +] + +[[package]] +name = "jsonway" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effcb749443c905fbaef49d214f8b1049c240e0adb7af9baa0e201e625e4f9de" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "jwtk" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6833c8be7e70530a018d6c48ef2a338b4d9df198ddb9d4ec0da436820a094526" +dependencies = [ + "base64 0.13.1", + "foreign-types", + "openssl", + "openssl-sys", + "reqwest", + "serde", + "serde_json", + "smallvec", + "tokio", +] + +[[package]] +name = "k256" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +dependencies = [ + "cfg-if", + "ecdsa 0.14.8", "elliptic-curve 0.12.3", "serdect", "sha2 0.10.8", @@ -3721,6 +5049,39 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "kvdb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d770dcb02bf6835887c3a979b5107a04ff4bbde97a5f0928d27404a155add9" +dependencies = [ + "smallvec", +] + +[[package]] +name = "kvdb-memorydb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7a85fe66f9ff9cd74e169fdd2c94c6e1e74c412c99a73b4df3200b5d3760b2" +dependencies = [ + "kvdb", + "parking_lot 0.12.1", +] + +[[package]] +name = "kvdb-rocksdb" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b644c70b92285f66bfc2032922a79000ea30af7bc2ab31902992a5dcb9b434f6" +dependencies = [ + "kvdb", + "num_cpus", + "parking_lot 0.12.1", + "regex", + "rocksdb", + "smallvec", +] + [[package]] name = "langtag" version = "0.3.4" @@ -3732,6 +5093,15 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lexical" @@ -3808,2441 +5178,4913 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.148" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" - -[[package]] -name = "libm" -version = "0.2.7" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] -name = "libsecp256k1" -version = "0.7.1" +name = "libloading" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ - "arrayref", - "base64 0.13.1", - "digest 0.9.0", - "hmac-drbg", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", - "rand 0.8.5", - "serde", - "sha2 0.9.9", - "typenum", + "cfg-if", + "winapi", ] [[package]] -name = "libsecp256k1-core" -version = "0.3.0" +name = "libm" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle", -] +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" +name = "libp2p" +version = "0.51.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +checksum = "f210d259724eae82005b5c48078619b7745edb7b76de370b03f8ba59ea103097" dependencies = [ - "libsecp256k1-core", + "bytes", + "futures", + "futures-timer", + "getrandom 0.2.10", + "instant", + "libp2p-allow-block-list", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-dns", + "libp2p-identify", + "libp2p-identity", + "libp2p-kad", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-noise", + "libp2p-ping", + "libp2p-quic", + "libp2p-request-response", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-wasm-ext", + "libp2p-webrtc", + "libp2p-websocket", + "libp2p-yamux", + "multiaddr", + "pin-project", ] [[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" +name = "libp2p-allow-block-list" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +checksum = "510daa05efbc25184458db837f6f9a5143888f1caa742426d92e1833ddd38a50" dependencies = [ - "libsecp256k1-core", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", ] [[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "linregress" -version = "0.5.3" +name = "libp2p-connection-limits" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de04dcecc58d366391f9920245b85ffa684558a5ef6e7736e754347c3aea9c2" +checksum = "4caa33f1d26ed664c4fe2cca81a08c8e07d4c1c04f2f4ac7655c2dd85467fda0" dependencies = [ - "nalgebra", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", ] [[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" +name = "libp2p-core" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "3c1df63c0b582aa434fb09b2d86897fa2b419ffeccf934b36f87fcedc8e835c2" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-identity", + "log", + "multiaddr", + "multihash", + "multistream-select", + "once_cell", + "parking_lot 0.12.1", + "pin-project", + "quick-protobuf", + "rand 0.8.5", + "rw-stream-sink", + "smallvec", + "thiserror", + "unsigned-varint", + "void", +] [[package]] -name = "linux-raw-sys" -version = "0.4.8" +name = "libp2p-dns" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" +checksum = "146ff7034daae62077c415c2376b8057368042df6ab95f5432ad5e88568b1554" +dependencies = [ + "futures", + "libp2p-core", + "log", + "parking_lot 0.12.1", + "smallvec", + "trust-dns-resolver", +] [[package]] -name = "lock_api" -version = "0.4.10" +name = "libp2p-identify" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "5455f472243e63b9c497ff320ded0314254a9eb751799a39c283c6f20b793f3c" dependencies = [ - "autocfg", - "scopeguard", + "asynchronous-codec", + "either", + "futures", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "log", + "lru", + "quick-protobuf", + "quick-protobuf-codec", + "smallvec", + "thiserror", + "void", ] [[package]] -name = "locspan" -version = "0.7.16" +name = "libp2p-identity" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eb541fabac50f90782ec4fafa7b0b3961adc0d1862c7c7a863dc94ed5541d73" +checksum = "276bb57e7af15d8f100d3c11cbdd32c6752b7eef4ba7a18ecf464972c07abcce" dependencies = [ - "contextual", - "hashbrown 0.13.2", + "bs58 0.4.0", + "ed25519-dalek", + "log", + "multiaddr", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "sha2 0.10.8", + "thiserror", + "zeroize", ] [[package]] -name = "locspan-derive" -version = "0.6.0" +name = "libp2p-kad" +version = "0.43.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88991223b049a3d29ca1f60c05639581336a0f3ee4bf8a659dddecc11c4961a" +checksum = "39d5ef876a2b2323d63c258e63c2f8e36f205fe5a11f0b3095d59635650790ff" dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", + "arrayvec 0.7.4", + "asynchronous-codec", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "log", + "quick-protobuf", + "rand 0.8.5", + "sha2 0.10.8", + "smallvec", + "thiserror", + "uint", + "unsigned-varint", + "void", ] [[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "lru" -version = "0.10.1" +name = "libp2p-mdns" +version = "0.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" +checksum = "19983e1f949f979a928f2c603de1cf180cc0dc23e4ac93a62651ccb18341460b" dependencies = [ - "hashbrown 0.13.2", + "data-encoding", + "futures", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "log", + "rand 0.8.5", + "smallvec", + "socket2 0.4.10", + "tokio", + "trust-dns-proto", + "void", ] [[package]] -name = "mach" -version = "0.3.2" +name = "libp2p-metrics" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +checksum = "a42ec91e227d7d0dafa4ce88b333cdf5f277253873ab087555c92798db2ddd46" dependencies = [ - "libc", + "libp2p-core", + "libp2p-identify", + "libp2p-kad", + "libp2p-ping", + "libp2p-swarm", + "prometheus-client", ] [[package]] -name = "mach2" -version = "0.4.1" +name = "libp2p-noise" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +checksum = "9c3673da89d29936bc6435bafc638e2f184180d554ce844db65915113f86ec5e" dependencies = [ - "libc", + "bytes", + "curve25519-dalek 3.2.0", + "futures", + "libp2p-core", + "libp2p-identity", + "log", + "once_cell", + "quick-protobuf", + "rand 0.8.5", + "sha2 0.10.8", + "snow", + "static_assertions", + "thiserror", + "x25519-dalek 1.1.1", + "zeroize", ] [[package]] -name = "macro-attr-2018" -version = "0.3.3" +name = "libp2p-ping" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe791c238364878e7c86d00b9237f7b6a309c75243beb6eab8132c396d2da37" +checksum = "3e57759c19c28a73ef1eb3585ca410cefb72c1a709fcf6de1612a378e4219202" +dependencies = [ + "either", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-swarm", + "log", + "rand 0.8.5", + "void", +] [[package]] -name = "macro_magic" -version = "0.4.2" +name = "libp2p-quic" +version = "0.7.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aee866bfee30d2d7e83835a4574aad5b45adba4cc807f2a3bbba974e5d4383c9" +checksum = "c6b26abd81cd2398382a1edfe739b539775be8a90fa6914f39b2ab49571ec735" dependencies = [ - "macro_magic_core", - "macro_magic_macros", - "quote", - "syn 2.0.37", + "bytes", + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "log", + "parking_lot 0.12.1", + "quinn-proto", + "rand 0.8.5", + "rustls 0.20.9", + "thiserror", + "tokio", ] [[package]] -name = "macro_magic_core" -version = "0.4.2" +name = "libp2p-request-response" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e766a20fd9c72bab3e1e64ed63f36bd08410e75803813df210d1ce297d7ad00" +checksum = "7ffdb374267d42dc5ed5bc53f6e601d4a64ac5964779c6e40bb9e4f14c1e30d5" dependencies = [ - "const-random", - "derive-syn-parse", - "macro_magic_core_macros", - "proc-macro2", - "quote", - "syn 2.0.37", + "async-trait", + "futures", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "smallvec", ] [[package]] -name = "macro_magic_core_macros" -version = "0.4.2" +name = "libp2p-swarm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c12469fc165526520dff2807c2975310ab47cf7190a45b99b49a7dc8befab17b" +checksum = "903b3d592d7694e56204d211f29d31bc004be99386644ba8731fc3e3ef27b296" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "log", + "rand 0.8.5", + "smallvec", + "tokio", + "void", ] [[package]] -name = "macro_magic_macros" -version = "0.4.2" +name = "libp2p-swarm-derive" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fb85ec1620619edf2984a7693497d4ec88a9665d8b87e942856884c92dbf2a" +checksum = "0fba456131824ab6acd4c7bf61e9c0f0a3014b5fc9868ccb8e10d344594cdc4f" dependencies = [ - "macro_magic_core", + "heck", "quote", - "syn 2.0.37", + "syn 1.0.109", ] [[package]] -name = "maplit" -version = "1.0.2" +name = "libp2p-tcp" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +checksum = "33d33698596d7722d85d3ab0c86c2c322254fce1241e91208e3679b4eb3026cf" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "log", + "socket2 0.4.10", + "tokio", +] [[package]] -name = "matchers" -version = "0.0.1" +name = "libp2p-tls" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +checksum = "ff08d13d0dc66e5e9ba6279c1de417b84fa0d0adc3b03e5732928c180ec02781" dependencies = [ - "regex-automata 0.1.10", + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen 0.10.0", + "ring 0.16.20", + "rustls 0.20.9", + "thiserror", + "webpki 0.22.4", + "x509-parser 0.14.0", + "yasna", ] [[package]] -name = "matchers" -version = "0.1.0" +name = "libp2p-wasm-ext" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "77dff9d32353a5887adb86c8afc1de1a94d9e8c3bc6df8b2201d7cdf5c848f43" dependencies = [ - "regex-automata 0.1.10", + "futures", + "js-sys", + "libp2p-core", + "parity-send-wrapper", + "wasm-bindgen", + "wasm-bindgen-futures", ] [[package]] -name = "matchit" -version = "0.7.3" +name = "libp2p-webrtc" +version = "0.4.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "dba48592edbc2f60b4bc7c10d65445b0c3964c07df26fdf493b6880d33be36f8" +dependencies = [ + "async-trait", + "asynchronous-codec", + "bytes", + "futures", + "futures-timer", + "hex", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "log", + "multihash", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "rcgen 0.9.3", + "serde", + "stun", + "thiserror", + "tinytemplate", + "tokio", + "tokio-util", + "webrtc", +] [[package]] -name = "matrixmultiply" -version = "0.3.8" +name = "libp2p-websocket" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +checksum = "111273f7b3d3510524c752e8b7a5314b7f7a1fee7e68161c01a7d72cbb06db9f" dependencies = [ - "autocfg", - "rawpointer", + "either", + "futures", + "futures-rustls", + "libp2p-core", + "log", + "parking_lot 0.12.1", + "quicksink", + "rw-stream-sink", + "soketto", + "url", + "webpki-roots 0.22.6", ] [[package]] -name = "memchr" -version = "2.6.4" +name = "libp2p-yamux" +version = "0.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "4dcd21d950662700a385d4c6d68e2f5f54d778e97068cdd718522222ef513bda" +dependencies = [ + "futures", + "libp2p-core", + "log", + "thiserror", + "yamux", +] [[package]] -name = "memfd" -version = "0.6.4" +name = "librocksdb-sys" +version = "0.11.0+8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" dependencies = [ - "rustix 0.38.17", + "bindgen", + "bzip2-sys", + "cc", + "glob", + "libc", + "libz-sys", + "tikv-jemalloc-sys", ] [[package]] -name = "memoffset" -version = "0.8.0" +name = "libsecp256k1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" dependencies = [ - "autocfg", + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", ] [[package]] -name = "memoffset" -version = "0.9.0" +name = "libsecp256k1-core" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" dependencies = [ - "autocfg", + "crunchy", + "digest 0.9.0", + "subtle", ] [[package]] -name = "memory-db" -version = "0.32.0" +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808b50db46293432a45e63bc15ea51e0ab4c0a1647b8eb114e31a3e698dd6fbe" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" dependencies = [ - "hash-db", + "libsecp256k1-core", ] [[package]] -name = "merlin" -version = "2.0.1" +name = "libsecp256k1-gen-genmult" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" dependencies = [ - "byteorder", - "keccak", - "rand_core 0.5.1", - "zeroize", + "libsecp256k1-core", ] [[package]] -name = "metadeps" -version = "1.1.2" +name = "libz-sys" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b122901b3a675fac8cecf68dcb2f0d3036193bc861d1ac0e1c337f7d5254c2" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ - "error-chain", + "cc", "pkg-config", - "toml 0.2.1", + "vcpkg", ] [[package]] -name = "metrics" -version = "0.21.1" +name = "link-cplusplus" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" dependencies = [ - "ahash 0.8.3", - "metrics-macros", - "portable-atomic", + "cc", ] [[package]] -name = "metrics-exporter-prometheus" -version = "0.12.1" +name = "linked-hash-map" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a4964177ddfdab1e3a2b37aec7cf320e14169abb0ed73999f558136409178d5" -dependencies = [ - "base64 0.21.4", - "hyper", - "indexmap 1.9.3", - "ipnet", - "metrics", - "metrics-util", - "quanta", - "thiserror", - "tokio", - "tracing", -] +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] -name = "metrics-macros" -version = "0.7.0" +name = "linked_hash_set" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" +checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", + "linked-hash-map", ] [[package]] -name = "metrics-util" -version = "0.15.0" +name = "linregress" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111cb375987443c3de8d503580b536f77dc8416d32db62d9456db5d93bd7ac47" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", - "hashbrown 0.13.2", - "metrics", - "num_cpus", - "quanta", - "sketches-ddsketch", -] - -[[package]] -name = "migrations_internals" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada" +checksum = "4de04dcecc58d366391f9920245b85ffa684558a5ef6e7736e754347c3aea9c2" dependencies = [ - "serde", - "toml 0.7.8", + "nalgebra", ] [[package]] -name = "migrations_macros" -version = "2.1.0" +name = "linux-raw-sys" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08" -dependencies = [ - "migrations_internals", - "proc-macro2", - "quote", -] +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] -name = "mime" -version = "0.3.17" +name = "linux-raw-sys" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "linux-raw-sys" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] -name = "miniz_oxide" -version = "0.7.1" +name = "lock_api" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ - "adler", + "autocfg", + "scopeguard", ] [[package]] -name = "mio" -version = "0.8.8" +name = "locspan" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "6eb541fabac50f90782ec4fafa7b0b3961adc0d1862c7c7a863dc94ed5541d73" dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "contextual", + "hashbrown 0.13.2", ] [[package]] -name = "mockito" -version = "1.2.0" +name = "locspan-derive" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d3038e23466858569c2d30a537f691fa0d53b51626630ae08262943e3bbb8b" +checksum = "e88991223b049a3d29ca1f60c05639581336a0f3ee4bf8a659dddecc11c4961a" dependencies = [ - "assert-json-diff", - "colored", - "futures", - "hyper", - "log", - "rand 0.8.5", - "regex", - "serde_json", - "serde_urlencoded", - "similar", - "tokio", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "mown" -version = "0.2.2" +name = "log" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7627d8bbeb17edbf1c3f74b21488e4af680040da89713b4217d0010e9cbd97e" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] -name = "multer" -version = "2.1.0" +name = "lru" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", - "memchr", - "mime", - "spin 0.9.8", - "version_check", + "hashbrown 0.13.2", ] [[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - -[[package]] -name = "nalgebra" -version = "0.32.3" +name = "lru-cache" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307ed9b18cc2423f29e83f84fd23a8e73628727990181f18641a8b5dc2ab1caa" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ - "approx", - "matrixmultiply", - "nalgebra-macros", - "num-complex", - "num-rational", - "num-traits", - "simba", - "typenum", + "linked-hash-map", ] [[package]] -name = "nalgebra-macros" -version = "0.2.1" +name = "lz4" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91761aed67d03ad966ef783ae962ef9bbaca728d2dd7ceb7939ec110fffad998" +checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "libc", + "lz4-sys", ] [[package]] -name = "native-tls" -version = "0.2.11" +name = "lz4-sys" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" dependencies = [ - "lazy_static", + "cc", "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", ] [[package]] -name = "newtype-derive-2018" -version = "0.2.1" +name = "mach" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "742e162dd2b575e1870f918cb1032962aca5ea2dfb6a6988ce1455e74a0f8e83" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" dependencies = [ - "generics", + "libc", ] [[package]] -name = "nix" -version = "0.27.1" +name = "mach2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" dependencies = [ - "bitflags 2.4.0", - "cfg-if", "libc", ] [[package]] -name = "no-std-net" -version = "0.5.0" +name = "macro-attr-2018" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bcece43b12349917e096cddfa66107277f123e6c96a5aea78711dc601a47152" -dependencies = [ - "serde", -] +checksum = "f21755d53936fc1663b414dba30636788f2183d3e782bdee4b1e7236637974e7" [[package]] -name = "nohash-hasher" -version = "0.2.0" +name = "macro_magic" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +checksum = "aee866bfee30d2d7e83835a4574aad5b45adba4cc807f2a3bbba974e5d4383c9" +dependencies = [ + "macro_magic_core", + "macro_magic_macros", + "quote", + "syn 2.0.38", +] [[package]] -name = "nom" -version = "7.1.3" +name = "macro_magic_core" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "7e766a20fd9c72bab3e1e64ed63f36bd08410e75803813df210d1ce297d7ad00" dependencies = [ - "memchr", - "minimal-lexical", + "const-random", + "derive-syn-parse", + "macro_magic_core_macros", + "proc-macro2", + "quote", + "syn 2.0.38", ] [[package]] -name = "nu-ansi-term" -version = "0.46.0" +name = "macro_magic_core_macros" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "d710e1214dffbab3b5dacb21475dde7d6ed84c69ff722b3a47a782668d44fbac" dependencies = [ - "overload", - "winapi", + "proc-macro2", + "quote", + "syn 2.0.38", ] [[package]] -name = "num" -version = "0.4.1" +name = "macro_magic_macros" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "b8fb85ec1620619edf2984a7693497d4ec88a9665d8b87e942856884c92dbf2a" dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", + "macro_magic_core", + "quote", + "syn 2.0.38", ] [[package]] -name = "num-bigint" -version = "0.4.4" +name = "maplit" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] -name = "num-cmp" +name = "match_cfg" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] -name = "num-complex" -version = "0.4.4" +name = "matchers" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" dependencies = [ - "num-traits", + "regex-automata 0.1.10", ] [[package]] -name = "num-format" -version = "0.4.4" +name = "matchers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "arrayvec 0.7.4", - "itoa", + "regex-automata 0.1.10", ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "matches" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] -name = "num-iter" -version = "0.1.43" +name = "matchit" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] -name = "num-rational" -version = "0.4.1" +name = "matrixmultiply" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" dependencies = [ "autocfg", - "num-bigint", - "num-integer", - "num-traits", + "rawpointer", ] [[package]] -name = "num-traits" -version = "0.2.16" +name = "md-5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "autocfg", - "libm", + "cfg-if", + "digest 0.10.7", ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "memchr" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.3", - "libc", -] +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] -name = "oauth-token" -version = "0.7.5" +name = "memfd" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "anyhow", - "oauth2", - "url", + "rustix 0.38.21", ] [[package]] -name = "oauth2" -version = "4.4.2" +name = "memmap2" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ - "base64 0.13.1", - "chrono", - "getrandom 0.2.10", - "http", - "rand 0.8.5", - "reqwest", - "serde", - "serde_json", - "serde_path_to_error", - "sha2 0.10.8", - "thiserror", - "url", + "libc", ] [[package]] -name = "object" -version = "0.30.4" +name = "memoffset" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "crc32fast", - "hashbrown 0.13.2", - "indexmap 1.9.3", - "memchr", + "autocfg", ] [[package]] -name = "object" -version = "0.32.1" +name = "memoffset" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ - "memchr", + "autocfg", ] [[package]] -name = "once_cell" -version = "1.18.0" +name = "memoffset" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] [[package]] -name = "oorandom" -version = "11.1.3" +name = "memory-db" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "808b50db46293432a45e63bc15ea51e0ab4c0a1647b8eb114e31a3e698dd6fbe" +dependencies = [ + "hash-db", +] [[package]] -name = "opa" -version = "0.9.0" -source = "git+https://github.com/tamasfe/opa-rs?rev=3cf7fea#3cf7fea850037fda7c353987db2d97ab90cc21f7" +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" dependencies = [ - "anyhow", - "bytes", - "flate2", - "serde", - "serde_json", - "tar", - "tempfile", - "thiserror", - "walkdir", - "wasmtime 10.0.2", - "which", + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", ] [[package]] -name = "opa-tp" -version = "0.7.5" +name = "metadeps" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b122901b3a675fac8cecf68dcb2f0d3036193bc861d1ac0e1c337f7d5254c2" dependencies = [ - "async-stl-client", - "async-trait", - "bytes", - "chronicle-signing", - "chronicle-telemetry", - "clap 3.2.25", - "const_format", - "hex", - "insta", - "k256 0.11.6", - "opa-tp-protocol", - "prost 0.10.4", - "protobuf", - "rand 0.8.5", - "rand_core 0.6.4", - "sawtooth-sdk", - "serde_json", - "thiserror", - "tokio", - "tracing", - "url", + "error-chain", + "pkg-config", + "toml 0.2.1", ] [[package]] -name = "opa-tp-protocol" -version = "0.7.5" +name = "metrics" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5" dependencies = [ - "async-stl-client", - "async-trait", - "chronicle-signing", - "derivative", - "futures", - "glob", - "hex", - "k256 0.11.6", - "lazy_static", - "openssl", - "prost 0.10.4", - "prost-build", - "prost-types 0.11.9", - "rand 0.8.5", - "rand_core 0.6.4", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "uuid 1.4.1", - "zmq", + "ahash 0.8.6", + "metrics-macros", + "portable-atomic", ] [[package]] -name = "opactl" -version = "0.7.5" +name = "metrics-exporter-prometheus" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a4964177ddfdab1e3a2b37aec7cf320e14169abb0ed73999f558136409178d5" dependencies = [ - "async-stl-client", - "async-trait", - "chronicle-signing", - "chronicle-telemetry", - "clap 4.4.6", - "common", - "const_format", - "futures", - "hex", - "insta", - "k256 0.11.6", - "lazy_static", - "opa-tp", - "opa-tp-protocol", - "portpicker", - "prost 0.10.4", - "protobuf", - "rand 0.8.5", - "rand_core 0.6.4", - "sawtooth-sdk", - "serde", - "serde_derive", - "serde_json", - "tempfile", + "base64 0.21.5", + "hyper", + "indexmap 1.9.3", + "ipnet", + "metrics", + "metrics-util", + "quanta", "thiserror", - "tmq", "tokio", - "tokio-stream", "tracing", - "url", - "user-error", - "uuid 1.4.1", ] [[package]] -name = "opaque-debug" -version = "0.2.3" +name = "metrics-macros" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] [[package]] -name = "opaque-debug" -version = "0.3.0" +name = "metrics-util" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "111cb375987443c3de8d503580b536f77dc8416d32db62d9456db5d93bd7ac47" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.13.2", + "metrics", + "num_cpus", + "quanta", + "sketches-ddsketch", +] [[package]] -name = "openssl" -version = "0.10.57" +name = "migrations_internals" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada" dependencies = [ - "bitflags 2.4.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", + "serde", + "toml 0.7.8", ] [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "migrations_macros" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08" dependencies = [ + "migrations_internals", "proc-macro2", "quote", - "syn 2.0.37", ] [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "openssl-sys" -version = "0.9.93" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "opentelemetry" -version = "0.19.0" +name = "miniz_oxide" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4b8347cc26099d3aeee044065ecc3ae11469796b4d65d065a23a584ed92a6f" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ - "opentelemetry_api 0.19.0", - "opentelemetry_sdk 0.19.0", + "adler", ] [[package]] -name = "opentelemetry" -version = "0.20.0" +name = "mio" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ - "opentelemetry_api 0.20.0", - "opentelemetry_sdk 0.20.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", ] [[package]] -name = "opentelemetry-http" -version = "0.9.0" +name = "mockall" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7594ec0e11d8e33faf03530a4c49af7064ebba81c1480e01be67d90b356508b" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" dependencies = [ - "async-trait", - "bytes", - "http", - "opentelemetry_api 0.20.0", + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates 2.1.5", + "predicates-tree", ] [[package]] -name = "opentelemetry-semantic-conventions" -version = "0.12.0" +name = "mockall_derive" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9f9340ad135068800e7f1b24e9e09ed9e7143f5bf8518ded3d3ec69789269" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ - "opentelemetry 0.20.0", + "cfg-if", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "opentelemetry_api" -version = "0.19.0" +name = "mockito" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed41783a5bf567688eb38372f2b7a8530f5a607a4b49d38dd7573236c23ca7e2" +checksum = "f8d3038e23466858569c2d30a537f691fa0d53b51626630ae08262943e3bbb8b" dependencies = [ - "futures-channel", - "futures-util", - "indexmap 1.9.3", - "once_cell", - "pin-project-lite", - "thiserror", - "urlencoding", + "assert-json-diff", + "colored", + "futures", + "hyper", + "log", + "rand 0.8.5", + "regex", + "serde_json", + "serde_urlencoded", + "similar", + "tokio", ] [[package]] -name = "opentelemetry_api" -version = "0.20.0" +name = "mown" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" -dependencies = [ - "futures-channel", - "futures-util", - "indexmap 1.9.3", - "js-sys", - "once_cell", - "pin-project-lite", - "thiserror", - "urlencoding", -] +checksum = "e7627d8bbeb17edbf1c3f74b21488e4af680040da89713b4217d0010e9cbd97e" [[package]] -name = "opentelemetry_sdk" -version = "0.19.0" +name = "multer" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" dependencies = [ - "async-trait", - "crossbeam-channel", - "futures-channel", - "futures-executor", + "bytes", + "encoding_rs", "futures-util", - "once_cell", - "opentelemetry_api 0.19.0", - "percent-encoding", - "rand 0.8.5", - "thiserror", - "tokio", - "tokio-stream", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin 0.9.8", + "version_check", ] [[package]] -name = "opentelemetry_sdk" -version = "0.20.0" +name = "multiaddr" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" +checksum = "2b36f567c7099511fa8612bbbb52dda2419ce0bdbacf31714e3a5ffdb766d3bd" dependencies = [ - "async-trait", - "crossbeam-channel", - "futures-channel", - "futures-executor", - "futures-util", - "once_cell", - "opentelemetry_api 0.20.0", - "ordered-float", + "arrayref", + "byteorder", + "data-encoding", + "log", + "multibase", + "multihash", "percent-encoding", - "rand 0.8.5", - "regex", - "thiserror", + "serde", + "static_assertions", + "unsigned-varint", + "url", ] [[package]] -name = "option-ext" -version = "0.2.0" +name = "multibase" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" +dependencies = [ + "base-x", + "data-encoding", + "data-encoding-macro", +] [[package]] -name = "ordered-float" -version = "3.9.1" +name = "multihash" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a54938017eacd63036332b4ae5c8a49fc8c0c1d6d629893057e4f13609edd06" +checksum = "835d6ff01d610179fbce3de1694d007e500bf33a7f29689838941d6bf783ae40" dependencies = [ - "num-traits", + "blake2b_simd", + "blake2s_simd", + "blake3", + "core2", + "digest 0.10.7", + "multihash-derive", + "sha2 0.10.8", + "sha3", + "unsigned-varint", ] [[package]] -name = "os_str_bytes" -version = "6.5.1" +name = "multihash-derive" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" +checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" +dependencies = [ + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] [[package]] -name = "overload" -version = "0.1.1" +name = "multimap" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] -name = "owo-colors" -version = "3.5.0" +name = "multistream-select" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +checksum = "c8552ab875c1313b97b8d20cb857b9fd63e2d1d6a0a1b53ce9821e575405f27a" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint", +] [[package]] -name = "parity-scale-codec" -version = "3.6.5" +name = "nalgebra" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" +checksum = "307ed9b18cc2423f29e83f84fd23a8e73628727990181f18641a8b5dc2ab1caa" dependencies = [ - "arrayvec 0.7.4", - "bitvec", - "byte-slice-cast", - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde", + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", ] [[package]] -name = "parity-scale-codec-derive" -version = "3.6.5" +name = "nalgebra-macros" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" +checksum = "91761aed67d03ad966ef783ae962ef9bbaca728d2dd7ceb7939ec110fffad998" dependencies = [ - "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] -name = "parity-wasm" -version = "0.45.0" +name = "names" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" +checksum = "e7d66043b25d4a6cccb23619d10c19c25304b355a7dccd4a8e11423dd2382146" +dependencies = [ + "rand 0.8.5", +] [[package]] -name = "parking_lot" -version = "0.12.1" +name = "native-tls" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ - "lock_api", - "parking_lot_core", + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] -name = "parking_lot_core" -version = "0.9.8" +name = "netlink-packet-core" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" dependencies = [ - "cfg-if", + "anyhow", + "byteorder", "libc", - "redox_syscall 0.3.5", - "smallvec", - "windows-targets 0.48.5", + "netlink-packet-utils", ] [[package]] -name = "paste" -version = "1.0.14" +name = "netlink-packet-route" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] [[package]] -name = "pbkdf2" -version = "0.8.0" +name = "netlink-packet-utils" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" -dependencies = [ - "crypto-mac 0.11.1", -] - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" dependencies = [ - "digest 0.10.7", + "anyhow", + "byteorder", + "paste", + "thiserror", ] [[package]] -name = "pct-str" -version = "1.2.0" +name = "netlink-proto" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d207ec8d182c2fef45f028b9b9507770df19e89b3e14827ccd95d4a23f6003" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" dependencies = [ - "utf8-decode", + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror", + "tokio", ] [[package]] -name = "pem-rfc7468" -version = "0.6.0" +name = "netlink-sys" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" dependencies = [ - "base64ct", + "bytes", + "futures", + "libc", + "log", + "tokio", ] [[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - -[[package]] -name = "permutohedron" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b687ff7b5da449d39e418ad391e5e08da53ec334903ddbb921db208908fc372c" - -[[package]] -name = "pest" -version = "2.7.4" +name = "newtype-derive-2018" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4" +checksum = "742e162dd2b575e1870f918cb1032962aca5ea2dfb6a6988ce1455e74a0f8e83" dependencies = [ - "memchr", - "thiserror", - "ucd-trie", + "generics", ] [[package]] -name = "pest_derive" -version = "2.7.4" +name = "nix" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "pest", - "pest_generator", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", ] [[package]] -name = "pest_generator" -version = "2.7.4" +name = "nix" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.37", + "bitflags 2.4.1", + "cfg-if", + "libc", ] [[package]] -name = "pest_meta" -version = "2.7.4" +name = "no-std-net" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d" +checksum = "1bcece43b12349917e096cddfa66107277f123e6c96a5aea78711dc601a47152" dependencies = [ - "once_cell", - "pest", - "sha2 0.10.8", + "serde", ] [[package]] -name = "petgraph" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +name = "node-chronicle" +version = "4.0.0-dev" dependencies = [ - "fixedbitset", - "indexmap 2.0.2", + "clap 4.4.7", + "frame-benchmarking", + "frame-benchmarking-cli", + "frame-system", + "futures", + "jsonrpsee", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc", + "runtime-chronicle", + "sc-basic-authorship", + "sc-cli", + "sc-client-api", + "sc-consensus", + "sc-consensus-aura", + "sc-consensus-grandpa", + "sc-executor", + "sc-network", + "sc-offchain", + "sc-rpc-api", + "sc-service", + "sc-statement-store", + "sc-telemetry", + "sc-transaction-pool", + "sc-transaction-pool-api", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-consensus-aura", + "sp-consensus-grandpa", + "sp-core", + "sp-inherents", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-timestamp", + "substrate-build-script-utils", + "substrate-frame-rpc-system", + "try-runtime-cli", ] [[package]] -name = "phf" -version = "0.8.0" +name = "nohash-hasher" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared", -] +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] -name = "phf_codegen" -version = "0.8.0" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "phf_generator", - "phf_shared", + "memchr", + "minimal-lexical", ] [[package]] -name = "phf_generator" -version = "0.8.0" +name = "normalize-line-endings" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared", - "rand 0.7.3", -] +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] -name = "phf_shared" -version = "0.8.0" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ - "siphasher", + "overload", + "winapi", ] [[package]] -name = "pin-project" -version = "1.1.3" +name = "num" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" dependencies = [ - "pin-project-internal", + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", ] [[package]] -name = "pin-project-internal" -version = "1.1.3" +name = "num-bigint" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", + "autocfg", + "num-integer", + "num-traits", ] [[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" +name = "num-cmp" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" [[package]] -name = "pinvec" -version = "0.1.0" +name = "num-complex" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "840ab949b9845c83495aba5cdfb23ab17aaa67b99ac36b17211bd02a5f9e6b36" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ - "pow_of_2", + "num-traits", ] [[package]] -name = "piper" -version = "0.2.1" +name = "num-format" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", + "arrayvec 0.7.4", + "itoa", ] [[package]] -name = "pkcs8" -version = "0.9.0" +name = "num-integer" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ - "der 0.6.1", - "spki 0.6.0", + "autocfg", + "num-traits", ] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "num-iter" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ - "der 0.7.8", - "spki 0.7.2", + "autocfg", + "num-integer", + "num-traits", ] [[package]] -name = "pkg-config" -version = "0.3.27" +name = "num-rational" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] [[package]] -name = "platforms" -version = "3.1.2" +name = "num-traits" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] [[package]] -name = "plotters" -version = "0.3.5" +name = "num_cpus" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", + "hermit-abi 0.3.3", + "libc", ] [[package]] -name = "plotters-backend" -version = "0.3.5" +name = "number_prefix" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] -name = "plotters-svg" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +name = "oauth-token" +version = "0.7.5" dependencies = [ - "plotters-backend", + "anyhow", + "oauth2", + "url", ] [[package]] -name = "poem" -version = "1.3.58" +name = "oauth2" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc7ae19f3e791ae8108b08801abb3708d64d3a16490c720e0b81040cae87b5d" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" dependencies = [ - "async-trait", - "base64 0.21.4", - "bytes", - "futures-util", - "headers", + "base64 0.13.1", + "chrono", + "getrandom 0.2.10", "http", - "hyper", - "mime", - "opentelemetry 0.20.0", - "opentelemetry-http", - "opentelemetry-semantic-conventions", - "parking_lot", - "percent-encoding", - "pin-project-lite", - "poem-derive", - "regex", - "rfc7239", + "rand 0.8.5", + "reqwest", "serde", "serde_json", - "serde_urlencoded", - "smallvec", + "serde_path_to_error", + "sha2 0.10.8", "thiserror", - "tokio", - "tokio-tungstenite", - "tokio-util", - "tracing", + "url", ] [[package]] -name = "poem-derive" -version = "1.3.58" +name = "object" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2550a0bce7273b278894ef3ccc5a6869e7031b6870042f3cc6826ed9faa980a6" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.37", + "crc32fast", + "hashbrown 0.13.2", + "indexmap 1.9.3", + "memchr", ] [[package]] -name = "portable-atomic" -version = "1.4.3" +name = "object" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] [[package]] -name = "portpicker" -version = "0.1.1" +name = "oid-registry" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a" dependencies = [ - "rand 0.8.5", + "asn1-rs 0.3.1", ] [[package]] -name = "pow_of_2" -version = "0.1.2" +name = "oid-registry" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4240ec1e46f35cb33cba66075d04778f53a5eba59ba4aecf2b1f8f6c4204d383" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs 0.5.2", +] [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "once_cell" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] -name = "pq-sys" -version = "0.4.8" +name = "oorandom" +version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "opa" +version = "0.9.0" +source = "git+https://github.com/tamasfe/opa-rs?rev=3cf7fea#3cf7fea850037fda7c353987db2d97ab90cc21f7" dependencies = [ - "vcpkg", + "anyhow", + "bytes", + "flate2", + "serde", + "serde_json", + "tar", + "tempfile", + "thiserror", + "walkdir", + "wasmtime 10.0.2", + "which", ] [[package]] -name = "predicates" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" +name = "opa-tp" +version = "0.7.5" dependencies = [ - "anstyle", - "difflib", - "itertools 0.11.0", - "predicates-core", + "async-stl-client", + "async-trait", + "bytes", + "chronicle-signing", + "chronicle-telemetry", + "clap 3.2.25", + "const_format", + "hex", + "insta", + "k256 0.11.6", + "opa-tp-protocol", + "prost 0.10.4", + "protobuf", + "rand 0.8.5", + "rand_core 0.6.4", + "sawtooth-sdk", + "serde_json", + "thiserror", + "tokio", + "tracing", + "url", ] [[package]] -name = "predicates-core" -version = "1.0.6" +name = "opa-tp-protocol" +version = "0.7.5" +dependencies = [ + "async-stl-client", + "async-trait", + "chronicle-signing", + "derivative", + "futures", + "glob", + "hex", + "k256 0.11.6", + "lazy_static", + "openssl", + "prost 0.10.4", + "prost-build 0.10.4", + "prost-types 0.11.9", + "rand 0.8.5", + "rand_core 0.6.4", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "uuid 1.5.0", + "zmq", +] + +[[package]] +name = "opactl" +version = "0.7.5" +dependencies = [ + "async-stl-client", + "async-trait", + "chronicle-signing", + "chronicle-telemetry", + "clap 4.4.7", + "common", + "const_format", + "futures", + "hex", + "insta", + "k256 0.11.6", + "lazy_static", + "opa-tp", + "opa-tp-protocol", + "portpicker", + "prost 0.10.4", + "protobuf", + "rand 0.8.5", + "rand_core 0.6.4", + "sawtooth-sdk", + "serde", + "serde_derive", + "serde_json", + "tempfile", + "thiserror", + "tmq", + "tokio", + "tokio-stream", + "tracing", + "url", + "user-error", + "uuid 1.5.0", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] -name = "predicates-tree" -version = "1.0.9" +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "predicates-core", - "termtree", + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", ] [[package]] -name = "pretty_dtoa" -version = "0.3.0" +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a239bcdfda2c685fda1add3b4695c06225f50075e3cfb5b954e91545587edff2" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "ryu_floating_decimal", + "proc-macro2", + "quote", + "syn 2.0.38", ] [[package]] -name = "primitive-types" -version = "0.12.1" +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ - "fixed-hash", - "impl-codec", - "impl-serde", - "scale-info", - "uint", + "cc", + "libc", + "pkg-config", + "vcpkg", ] [[package]] -name = "proc-macro-crate" -version = "1.3.1" +name = "opentelemetry" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "5f4b8347cc26099d3aeee044065ecc3ae11469796b4d65d065a23a584ed92a6f" dependencies = [ - "once_cell", - "toml_edit", + "opentelemetry_api 0.19.0", + "opentelemetry_sdk 0.19.0", +] + +[[package]] +name = "opentelemetry" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" +dependencies = [ + "opentelemetry_api 0.20.0", + "opentelemetry_sdk 0.20.0", +] + +[[package]] +name = "opentelemetry-http" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7594ec0e11d8e33faf03530a4c49af7064ebba81c1480e01be67d90b356508b" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry_api 0.20.0", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c9f9340ad135068800e7f1b24e9e09ed9e7143f5bf8518ded3d3ec69789269" +dependencies = [ + "opentelemetry 0.20.0", +] + +[[package]] +name = "opentelemetry_api" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed41783a5bf567688eb38372f2b7a8530f5a607a4b49d38dd7573236c23ca7e2" +dependencies = [ + "futures-channel", + "futures-util", + "indexmap 1.9.3", + "once_cell", + "pin-project-lite 0.2.13", + "thiserror", + "urlencoding", +] + +[[package]] +name = "opentelemetry_api" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" +dependencies = [ + "futures-channel", + "futures-util", + "indexmap 1.9.3", + "js-sys", + "once_cell", + "pin-project-lite 0.2.13", + "thiserror", + "urlencoding", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "once_cell", + "opentelemetry_api 0.19.0", + "percent-encoding", + "rand 0.8.5", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "once_cell", + "opentelemetry_api 0.20.0", + "ordered-float", + "percent-encoding", + "rand 0.8.5", + "regex", + "thiserror", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2 0.10.8", +] + +[[package]] +name = "p384" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa" +dependencies = [ + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2 0.10.8", +] + +[[package]] +name = "pallet-aura" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7e2b1cf20dbd9fe630c69b4b0d3bb0d5fa1223ee728b0fc0064ef65698918c2" +dependencies = [ + "frame-support", + "frame-system", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-aura", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-authorship" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae682e78744224150298730dfa1e2c39220e600dce17e42d2c77e49af3d9c59f" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-balances" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c17ec19ad23b26866ad7d60cdf8b613f653db7f44232aa25009811441908e2b" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-chronicle" +version = "0.7.5" +dependencies = [ + "chronicle-telemetry", + "common", + "frame-benchmarking", + "frame-support", + "frame-system", + "macro-attr-2018", + "newtype-derive-2018", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "tracing", + "uuid 1.5.0", +] + +[[package]] +name = "pallet-grandpa" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "977d01d5ce3f06fa17adf2ffa55ebaea765efa23bc11a242773a28955ee1d02b" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-grandpa", + "sp-core", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-session" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8482f465a73688a7d58e20dea4b10c9a0425995975b2a43d9ce4fe9a21a491" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-state-machine", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-sudo" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "679c265de3a128714d43a7e2edf5ea29f2a39df65e4c44e216c04d6bb5dd5be7" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-timestamp" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac4e66316d53673471420fb887b6a74e2507df169ced62584507ff0fb065c6b" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-std", + "sp-storage", + "sp-timestamp", +] + +[[package]] +name = "pallet-transaction-payment" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4cbb78b8499af1d338072950e4aef6acf3cc630afdb8e19b00306e5252d0386" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-transaction-payment-rpc" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7857973b918e71367acb7c284c829612aa9c91a6ba1fb2985d56fbe224545" +dependencies = [ + "jsonrpsee", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-weights", +] + +[[package]] +name = "pallet-transaction-payment-rpc-runtime-api" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402155004abb33b7f2eedfa60ba77fb6f898e62db979a796e013714d18a1c9c2" +dependencies = [ + "pallet-transaction-payment", + "parity-scale-codec", + "sp-api", + "sp-runtime", + "sp-weights", +] + +[[package]] +name = "parity-db" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e9ab494af9e6e813c72170f0d3c1de1500990d62c97cc05cc7576f91aa402f" +dependencies = [ + "blake2", + "crc32fast", + "fs2", + "hex", + "libc", + "log", + "lz4", + "memmap2", + "parking_lot 0.12.1", + "rand 0.8.5", + "siphasher", + "snap", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" +dependencies = [ + "arrayvec 0.7.4", + "bitvec", + "byte-slice-cast", + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parity-send-wrapper" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" + +[[package]] +name = "parity-wasm" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "partial_sort" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7924d1d0ad836f665c9065e26d016c673ece3993f30d340068b16f282afc1156" + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "crypto-mac 0.11.1", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "pct-str" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d207ec8d182c2fef45f028b9b9507770df19e89b3e14827ccd95d4a23f6003" +dependencies = [ + "utf8-decode", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "permutohedron" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b687ff7b5da449d39e418ad391e5e08da53ec334903ddbb921db208908fc372c" + +[[package]] +name = "pest" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "pest_meta" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +dependencies = [ + "once_cell", + "pest", + "sha2 0.10.8", +] + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.0.2", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pinvec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840ab949b9845c83495aba5cdfb23ab17aaa67b99ac36b17211bd02a5f9e6b36" +dependencies = [ + "pow_of_2", +] + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.8", + "spki 0.7.2", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "platforms" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "poem" +version = "1.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc7ae19f3e791ae8108b08801abb3708d64d3a16490c720e0b81040cae87b5d" +dependencies = [ + "async-trait", + "base64 0.21.5", + "bytes", + "futures-util", + "headers", + "http", + "hyper", + "mime", + "opentelemetry 0.20.0", + "opentelemetry-http", + "opentelemetry-semantic-conventions", + "parking_lot 0.12.1", + "percent-encoding", + "pin-project-lite 0.2.13", + "poem-derive", + "regex", + "rfc7239", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "thiserror", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tracing", +] + +[[package]] +name = "poem-derive" +version = "1.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2550a0bce7273b278894ef3ccc5a6869e7031b6870042f3cc6826ed9faa980a6" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite 0.2.13", + "windows-sys 0.48.0", +] + +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash 0.4.1", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash 0.4.1", +] + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash 0.5.1", +] + +[[package]] +name = "portable-atomic" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" + +[[package]] +name = "portpicker" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "pow_of_2" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4240ec1e46f35cb33cba66075d04778f53a5eba59ba4aecf2b1f8f6c4204d383" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pq-sys" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd" +dependencies = [ + "vcpkg", +] + +[[package]] +name = "predicates" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +dependencies = [ + "difflib", + "float-cmp", + "itertools 0.10.5", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" +dependencies = [ + "anstyle", + "difflib", + "itertools 0.11.0", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "pretty_dtoa" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a239bcdfda2c685fda1add3b4695c06225f50075e3cfb5b954e91545587edff2" +dependencies = [ + "ryu_floating_decimal", +] + +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.38", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml 0.5.11", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-warning" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.12.1", + "thiserror", +] + +[[package]] +name = "prometheus-client" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6fa99d535dd930d1249e6c79cb3c2915f9172a540fe2b02a4c8f9ca954721e" +dependencies = [ + "dtoa", + "itoa", + "parking_lot 0.12.1", + "prometheus-client-derive-encode", +] + +[[package]] +name = "prometheus-client-derive-encode" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "proptest" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.4.1", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax 0.7.5", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "prost" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +dependencies = [ + "bytes", + "prost-derive 0.10.1", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost-build" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" +dependencies = [ + "bytes", + "cfg-if", + "cmake", + "heck", + "itertools 0.10.5", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost 0.10.4", + "prost-types 0.10.1", + "regex", + "tempfile", + "which", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck", + "itertools 0.10.5", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease 0.1.25", + "prost 0.11.9", + "prost-types 0.11.9", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" +dependencies = [ + "bytes", + "prost 0.10.4", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[package]] +name = "protobuf-codegen" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6" +dependencies = [ + "protobuf", +] + +[[package]] +name = "protoc" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0218039c514f9e14a5060742ecd50427f8ac4f85a6dc58f2ddb806e318c55ee" +dependencies = [ + "log", + "which", +] + +[[package]] +name = "protoc-rust" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f8a182bb17c485f20bdc4274a8c39000a61024cfe461c799b50fec77267838" +dependencies = [ + "protobuf", + "protobuf-codegen", + "protoc", + "tempfile", +] + +[[package]] +name = "psl" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1be0afcd844b15cfce18bf8cccf2dfa887a00a6454a9ea135f122b948cee91" +dependencies = [ + "psl-types", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "quanta" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +dependencies = [ + "crossbeam-utils", + "libc", + "mach2", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + +[[package]] +name = "question" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbb3ede7a8f9a8ab89e714637f2cf40001b58f21340d4242b2f11533e65fa8d" + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quick-protobuf-codec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1693116345026436eb2f10b677806169c1a1260c1c60eaaffe3fb5a29ae23d8b" +dependencies = [ + "asynchronous-codec", + "bytes", + "quick-protobuf", + "thiserror", + "unsigned-varint", +] + +[[package]] +name = "quicksink" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77de3c815e5a160b1539c6592796801df2043ae35e123b46d73380cfa57af858" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project-lite 0.1.12", +] + +[[package]] +name = "quinn-proto" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b0b33c13a79f669c85defaf4c275dc86a0c0372807d0ca3d78e0bb87274863" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring 0.16.20", + "rustc-hash", + "rustls 0.20.9", + "slab", + "thiserror", + "tinyvec", + "tracing", + "webpki 0.22.4", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot 0.12.1", + "scheduled-thread-pool", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg 0.2.1", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rcgen" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" +dependencies = [ + "pem", + "ring 0.16.20", + "time", + "x509-parser 0.13.2", + "yasna", +] + +[[package]] +name = "rcgen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" +dependencies = [ + "pem", + "ring 0.16.20", + "time", + "yasna", +] + +[[package]] +name = "rdf-types" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9501694cabcbf4856344831d0c0e74e6332bf8ca3196a906e3c95eb2d14d07d8" +dependencies = [ + "contextual", + "iref", + "langtag", + "locspan", + "locspan-derive", + "thiserror", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.10", + "redox_syscall 0.2.16", + "thiserror", +] + +[[package]] +name = "ref-cast" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acde58d073e9c79da00f2b5b84eed919c8326832648a5b109b3fce1bb1175280" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "regalloc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80535183cae11b149d618fbd3c37e38d7cda589d82d7769e196ca9a9042d7621" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relative-path" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" + +[[package]] +name = "reqwest" +version = "0.11.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +dependencies = [ + "base64 0.21.5", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite 0.2.13", + "rustls 0.21.8", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.2", + "winreg", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "rfc6979" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", + "crypto-bigint 0.4.9", + "hmac 0.12.1", + "zeroize", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "hmac 0.12.1", + "subtle", ] [[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" +name = "rfc7239" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" +checksum = "087317b3cf7eb481f13bd9025d729324b7cd068d6f470e2d76d049e191f5ba47" +dependencies = [ + "uncased", +] [[package]] -name = "proc-macro-warning" -version = "0.4.2" +name = "ring" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", ] [[package]] -name = "proc-macro2" -version = "1.0.67" +name = "ring" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" dependencies = [ - "unicode-ident", + "cc", + "getrandom 0.2.10", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", ] [[package]] -name = "proptest" -version = "1.3.1" +name = "rocksdb" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" +checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.4.0", - "lazy_static", - "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_xorshift", - "regex-syntax 0.7.5", - "rusty-fork", - "tempfile", - "unarray", + "libc", + "librocksdb-sys", ] [[package]] -name = "prost" -version = "0.10.4" +name = "rpassword" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" dependencies = [ - "bytes", - "prost-derive 0.10.1", + "libc", + "rtoolbox", + "winapi", ] [[package]] -name = "prost" -version = "0.11.9" +name = "rsb_derive" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "b2c53e42fccdc5f1172e099785fe78f89bc0c1e657d0c2ef591efbfac427e9a4" dependencies = [ - "bytes", - "prost-derive 0.11.9", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "prost-build" -version = "0.10.4" +name = "rtcp" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" +checksum = "1919efd6d4a6a85d13388f9487549bb8e359f17198cc03ffd72f79b553873691" dependencies = [ "bytes", - "cfg-if", - "cmake", - "heck", - "itertools 0.10.5", - "lazy_static", - "log", - "multimap", - "petgraph", - "prost 0.10.4", - "prost-types 0.10.1", - "regex", - "tempfile", - "which", + "thiserror", + "webrtc-util", ] [[package]] -name = "prost-derive" +name = "rtnetlink" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 1.0.109", + "futures", + "log", + "netlink-packet-route", + "netlink-proto", + "nix 0.24.3", + "thiserror", + "tokio", ] [[package]] -name = "prost-derive" -version = "0.11.9" +name = "rtoolbox" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 1.0.109", + "libc", + "winapi", ] [[package]] -name = "prost-types" -version = "0.10.1" +name = "rtp" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" +checksum = "a2a095411ff00eed7b12e4c6a118ba984d113e1079582570d56a5ee723f11f80" dependencies = [ + "async-trait", "bytes", - "prost 0.10.4", + "rand 0.8.5", + "serde", + "thiserror", + "webrtc-util", ] [[package]] -name = "prost-types" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +name = "runtime-chronicle" +version = "4.0.0" dependencies = [ - "prost 0.11.9", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "pallet-aura", + "pallet-balances", + "pallet-chronicle", + "pallet-grandpa", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-consensus-grandpa", + "sp-core", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-transaction-pool", + "sp-version", + "substrate-wasm-builder", ] [[package]] -name = "protobuf" -version = "2.28.0" +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] -name = "protobuf-codegen" -version = "2.28.0" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6" -dependencies = [ - "protobuf", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "protoc" -version = "2.28.0" +name = "rustc-hex" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0218039c514f9e14a5060742ecd50427f8ac4f85a6dc58f2ddb806e318c55ee" -dependencies = [ - "log", - "which", -] +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] -name = "protoc-rust" -version = "2.28.0" +name = "rustc_version" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f8a182bb17c485f20bdc4274a8c39000a61024cfe461c799b50fec77267838" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "protobuf", - "protobuf-codegen", - "protoc", - "tempfile", + "semver 1.0.20", ] [[package]] -name = "psl" -version = "2.1.4" +name = "rusticata-macros" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1be0afcd844b15cfce18bf8cccf2dfa887a00a6454a9ea135f122b948cee91" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ - "psl-types", + "nom", ] [[package]] -name = "psl-types" -version = "2.0.11" +name = "rustify" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" +checksum = "e9c02e25271068de581e03ac3bb44db60165ff1a10d92b9530192ccb898bc706" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "http", + "reqwest", + "rustify_derive", + "serde", + "serde_json", + "serde_urlencoded", + "thiserror", + "tracing", + "url", +] [[package]] -name = "psm" -version = "0.1.21" +name = "rustify_derive" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "58135536c18c04f4634bedad182a3f41baf33ef811cc38a3ec7b7061c57134c8" dependencies = [ - "cc", + "proc-macro2", + "quote", + "regex", + "serde_urlencoded", + "syn 1.0.109", + "synstructure", ] [[package]] -name = "quanta" -version = "0.11.1" +name = "rustix" +version = "0.36.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" dependencies = [ - "crossbeam-utils", + "bitflags 1.3.2", + "errno", + "io-lifetimes", "libc", - "mach2", - "once_cell", - "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", - "web-sys", - "winapi", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", ] [[package]] -name = "question" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbb3ede7a8f9a8ab89e714637f2cf40001b58f21340d4242b2f11533e65fa8d" - -[[package]] -name = "quick-error" -version = "1.2.3" +name = "rustix" +version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] [[package]] -name = "quote" -version = "1.0.33" +name = "rustix" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "proc-macro2", + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys 0.4.10", + "windows-sys 0.48.0", ] [[package]] -name = "r2d2" -version = "0.8.10" +name = "rustls" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ + "base64 0.13.1", "log", - "parking_lot", - "scheduled-thread-pool", + "ring 0.16.20", + "sct 0.6.1", + "webpki 0.21.4", ] [[package]] -name = "radium" -version = "0.7.0" +name = "rustls" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct 0.7.1", + "webpki 0.22.4", +] [[package]] -name = "rand" -version = "0.7.3" +name = "rustls" +version = "0.21.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", + "log", + "ring 0.17.5", + "rustls-webpki", + "sct 0.7.1", ] [[package]] -name = "rand" -version = "0.8.5" +name = "rustls-native-certs" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", ] [[package]] -name = "rand_chacha" -version = "0.2.2" +name = "rustls-pemfile" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "base64 0.21.5", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "rustls-webpki" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] -name = "rand_core" -version = "0.5.1" +name = "rustversion" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] -name = "rand_core" -version = "0.6.4" +name = "rusty-fork" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ - "getrandom 0.2.10", + "fnv", + "quick-error", + "tempfile", + "wait-timeout", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rvs_derive" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "6e1fa12378eb54f3d4f2db8dcdbe33af610b7e7d001961c1055858282ecef2a5" dependencies = [ - "rand_core 0.5.1", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "rand_pcg" -version = "0.2.1" +name = "rvstruct" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +checksum = "5107860ec34506b64cf3680458074eac5c2c564f7ccc140918bbcd1714fd8d5d" dependencies = [ - "rand_core 0.5.1", + "rvs_derive", ] [[package]] -name = "rand_xorshift" +name = "rw-stream-sink" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "26338f5e09bb721b85b135ea05af7767c90b52f6de4f087d4f4a3a9d64e7dc04" dependencies = [ - "rand_core 0.6.4", + "futures", + "pin-project", + "static_assertions", ] [[package]] -name = "raw-cpuid" -version = "10.7.0" +name = "ryu" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" -dependencies = [ - "bitflags 1.3.2", -] +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] -name = "rawpointer" -version = "0.2.1" +name = "ryu-js" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" +checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" [[package]] -name = "rayon" -version = "1.8.0" +name = "ryu_floating_decimal" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" -dependencies = [ - "either", - "rayon-core", -] +checksum = "700de91d5fd6091442d00fdd9ee790af6d4f0f480562b0f5a1e8f59e90aafe73" [[package]] -name = "rayon-core" -version = "1.12.0" +name = "safe_arch" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "bytemuck", ] [[package]] -name = "rdf-types" -version = "0.14.9" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9501694cabcbf4856344831d0c0e74e6332bf8ca3196a906e3c95eb2d14d07d8" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "contextual", - "iref", - "langtag", - "locspan", - "locspan-derive", - "thiserror", + "winapi-util", ] [[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +name = "sawtooth-sdk" +version = "0.5.3" +source = "git+https://github.com/hyperledger/sawtooth-sdk-rust?rev=5a300de#5a300de293666aa056a2970a39651d4dd5098885" dependencies = [ - "bitflags 1.3.2", + "ctrlc", + "glob", + "hex", + "libc", + "log", + "openssl", + "protobuf", + "protoc-rust", + "rand 0.8.5", + "secp256k1 0.27.0", + "sha2 0.10.8", + "uuid 0.8.2", + "zmq", ] [[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +name = "sawtooth_tp" +version = "0.7.5" dependencies = [ - "bitflags 1.3.2", + "async-stl-client", + "async-trait", + "bytes", + "chronicle-protocol", + "chronicle-signing", + "chronicle-telemetry", + "chrono", + "clap 3.2.25", + "common", + "const_format", + "custom_error", + "derivative", + "futures", + "glob", + "hex", + "insta", + "lazy_static", + "opa-tp-protocol", + "openssl", + "opentelemetry 0.19.0", + "prost 0.10.4", + "protobuf", + "rand 0.8.5", + "rand_core 0.6.4", + "sawtooth-sdk", + "serde", + "serde_derive", + "serde_json", + "tempfile", + "tokio", + "tracing", + "url", + "uuid 1.5.0", + "zmq", ] [[package]] -name = "redox_users" -version = "0.4.3" +name = "sc-allocator" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "2bd6e58990dcb1eae76db49c456ded9a7906ee194857cf1dfb00da8bbc8cf73d" dependencies = [ - "getrandom 0.2.10", - "redox_syscall 0.2.16", + "log", + "sp-core", + "sp-wasm-interface", "thiserror", ] [[package]] -name = "ref-cast" -version = "1.0.20" +name = "sc-basic-authorship" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acde58d073e9c79da00f2b5b84eed919c8326832648a5b109b3fce1bb1175280" +checksum = "f0f802a95cece137daa3a0980f41a8e9265aa65d1b078f8d771f7d2f41e04266" dependencies = [ - "ref-cast-impl", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "sc-block-builder", + "sc-client-api", + "sc-proposer-metrics", + "sc-telemetry", + "sc-transaction-pool-api", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-inherents", + "sp-runtime", + "substrate-prometheus-endpoint", ] [[package]] -name = "ref-cast-impl" -version = "1.0.20" +name = "sc-block-builder" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" +checksum = "4653cc3665319f76451f651bc5e3eb84965802293daeaf2def5bfe9c1310171b" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", + "parity-scale-codec", + "sc-client-api", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-core", + "sp-inherents", + "sp-runtime", ] [[package]] -name = "regalloc2" -version = "0.9.3" +name = "sc-chain-spec" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +checksum = "e5fae1616d342e570fb4770c9f1a73ab8e1aecb9c5b71020404f8e45db458260" dependencies = [ - "hashbrown 0.13.2", - "log", - "rustc-hash", - "slice-group-by", - "smallvec", + "memmap2", + "sc-chain-spec-derive", + "sc-client-api", + "sc-executor", + "sc-network", + "sc-telemetry", + "serde", + "serde_json", + "sp-blockchain", + "sp-core", + "sp-runtime", + "sp-state-machine", ] [[package]] -name = "regex" -version = "1.9.6" +name = "sc-chain-spec-derive" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "88a074891d17c03c58b1314c9add361a5a7fb28d4d3addd7a32dca8b119bd877" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.3.9", - "regex-syntax 0.7.5", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.38", ] [[package]] -name = "regex-automata" -version = "0.1.10" +name = "sc-cli" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "bc423e21a22adc4f6056ccb5e19fca9ddc6cce1a49cd9aa44c53d6b2338fbeb3" dependencies = [ - "regex-syntax 0.6.29", + "array-bytes", + "chrono", + "clap 4.4.7", + "fdlimit", + "futures", + "libp2p-identity", + "log", + "names", + "parity-scale-codec", + "rand 0.8.5", + "regex", + "rpassword", + "sc-client-api", + "sc-client-db", + "sc-keystore", + "sc-network", + "sc-service", + "sc-telemetry", + "sc-tracing", + "sc-utils", + "serde", + "serde_json", + "sp-blockchain", + "sp-core", + "sp-keyring", + "sp-keystore", + "sp-panic-handler", + "sp-runtime", + "sp-version", + "thiserror", + "tiny-bip39", + "tokio", ] [[package]] -name = "regex-automata" -version = "0.3.9" +name = "sc-client-api" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "d49efb455b1b276557ba3cac01c2e42811148cc73149858296e4ae96707dc70e" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.7.5", + "fnv", + "futures", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-executor", + "sc-transaction-pool-api", + "sc-utils", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-database", + "sp-externalities", + "sp-runtime", + "sp-state-machine", + "sp-statement-store", + "sp-storage", + "sp-trie", + "substrate-prometheus-endpoint", ] [[package]] -name = "regex-syntax" -version = "0.6.29" +name = "sc-client-db" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "a1062af3e43f09e0080714382ee3e7dd850037908938323eefdcd4f4b61bdd6b" +dependencies = [ + "hash-db", + "kvdb", + "kvdb-memorydb", + "kvdb-rocksdb", + "linked-hash-map", + "log", + "parity-db", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-client-api", + "sc-state-db", + "schnellru", + "sp-arithmetic", + "sp-blockchain", + "sp-core", + "sp-database", + "sp-runtime", + "sp-state-machine", + "sp-trie", +] [[package]] -name = "regex-syntax" -version = "0.7.5" +name = "sc-consensus" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "f5f8da1ef0f036209b80d8bde5c8990ea1a86241532d84b5fd15f5e721da849c" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "libp2p-identity", + "log", + "mockall", + "parking_lot 0.12.1", + "sc-client-api", + "sc-utils", + "serde", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-runtime", + "sp-state-machine", + "substrate-prometheus-endpoint", + "thiserror", +] [[package]] -name = "relative-path" -version = "1.9.0" +name = "sc-consensus-aura" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" +checksum = "4cbb98be43737b79517f2de34b5185a58238dc8f69e84ddf7e4730bc0e2e2e65" +dependencies = [ + "async-trait", + "futures", + "log", + "parity-scale-codec", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-consensus-slots", + "sc-telemetry", + "sp-api", + "sp-application-crypto", + "sp-block-builder", + "sp-blockchain", + "sp-consensus", + "sp-consensus-aura", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-keystore", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", +] [[package]] -name = "reqwest" -version = "0.11.22" +name = "sc-consensus-grandpa" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "98285bbed76ba058f3c9f4111471208cd4c246d2ca7b52b7fbea15afdbb40ca5" dependencies = [ - "base64 0.21.4", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "hyper-tls", - "ipnet", - "js-sys", + "ahash 0.8.6", + "array-bytes", + "async-trait", + "dyn-clone", + "finality-grandpa", + "fork-tree", + "futures", + "futures-timer", "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", + "parity-scale-codec", + "parking_lot 0.12.1", + "rand 0.8.5", + "sc-block-builder", + "sc-chain-spec", + "sc-client-api", + "sc-consensus", + "sc-network", + "sc-network-common", + "sc-network-gossip", + "sc-telemetry", + "sc-transaction-pool-api", + "sc-utils", "serde_json", - "serde_urlencoded", - "system-configuration", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", + "sp-api", + "sp-application-crypto", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-consensus-grandpa", + "sp-core", + "sp-keystore", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", ] [[package]] -name = "rfc6979" -version = "0.3.1" +name = "sc-consensus-slots" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +checksum = "a0a232d18eb53288775eaeb782cad70ca75815cd10e6212352e6e53cc3961931" dependencies = [ - "crypto-bigint 0.4.9", - "hmac 0.12.1", - "zeroize", + "async-trait", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "sc-client-api", + "sc-consensus", + "sc-telemetry", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-state-machine", ] [[package]] -name = "rfc6979" -version = "0.4.0" +name = "sc-executor" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "5cfeaa8dc2a70ed5820667d3251266ed156f38d8062c2f976aa7c618411f1776" dependencies = [ - "hmac 0.12.1", - "subtle", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-executor-common", + "sc-executor-wasmtime", + "schnellru", + "sp-api", + "sp-core", + "sp-externalities", + "sp-io", + "sp-panic-handler", + "sp-runtime-interface", + "sp-trie", + "sp-version", + "sp-wasm-interface", + "tracing", ] [[package]] -name = "rfc7239" -version = "0.1.0" +name = "sc-executor-common" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "087317b3cf7eb481f13bd9025d729324b7cd068d6f470e2d76d049e191f5ba47" +checksum = "5d404519f2a636d5977b1ac16c90aeb4129fe4609a5b284960a2dcb005c08da6" dependencies = [ - "uncased", + "sc-allocator", + "sp-maybe-compressed-blob", + "sp-wasm-interface", + "thiserror", + "wasm-instrument", ] [[package]] -name = "ring" -version = "0.16.20" +name = "sc-executor-wasmtime" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "a82515a0cb74a2acb58f6ced20fae56eeb87ba4d813e60e46cf190a53d44c931" dependencies = [ - "cc", + "anyhow", + "cfg-if", "libc", - "once_cell", - "spin 0.5.2", - "untrusted", - "web-sys", - "winapi", + "log", + "rustix 0.36.17", + "sc-allocator", + "sc-executor-common", + "sp-runtime-interface", + "sp-wasm-interface", + "wasmtime 8.0.1", ] [[package]] -name = "rsb_derive" -version = "0.5.1" +name = "sc-informant" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c53e42fccdc5f1172e099785fe78f89bc0c1e657d0c2ef591efbfac427e9a4" +checksum = "233ece6736217208ffac94f84de2d15465f80f676f881dacd0a9b3411b476951" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "ansi_term", + "futures", + "futures-timer", + "log", + "sc-client-api", + "sc-network", + "sc-network-common", + "sp-blockchain", + "sp-runtime", ] [[package]] -name = "rust-embed" -version = "6.8.1" +name = "sc-keystore" +version = "21.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" +checksum = "1c15cc8b79eb0832cac48fde41e9ecd011df5d57dad7608f2b89fe721e97012c" dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", + "array-bytes", + "parking_lot 0.12.1", + "serde_json", + "sp-application-crypto", + "sp-core", + "sp-keystore", + "thiserror", ] [[package]] -name = "rust-embed-impl" -version = "6.8.1" +name = "sc-network" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" +checksum = "3edad0e7930c2572d6920dc257bc03af6f40ba272bc45602edd0a045d94e5e59" dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.37", - "walkdir", + "array-bytes", + "async-channel", + "async-trait", + "asynchronous-codec", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "ip_network", + "libp2p", + "linked_hash_set", + "log", + "mockall", + "parity-scale-codec", + "parking_lot 0.12.1", + "partial_sort", + "pin-project", + "rand 0.8.5", + "sc-client-api", + "sc-network-common", + "sc-utils", + "serde", + "serde_json", + "smallvec", + "sp-arithmetic", + "sp-blockchain", + "sp-core", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", + "unsigned-varint", + "wasm-timer", + "zeroize", ] [[package]] -name = "rust-embed-utils" -version = "7.8.1" +name = "sc-network-bitswap" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" +checksum = "e6a0d247f576989cb2fe49df0511cbbd826f1e47b444848971e2bddec8f18a65" dependencies = [ - "globset", - "sha2 0.10.8", - "walkdir", + "async-channel", + "cid", + "futures", + "libp2p-identity", + "log", + "prost 0.11.9", + "prost-build 0.11.9", + "sc-client-api", + "sc-network", + "sp-blockchain", + "sp-runtime", + "thiserror", + "unsigned-varint", ] [[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hex" -version = "2.1.0" +name = "sc-network-common" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +checksum = "b418c79cea8ab5b43f5bbe7ee95da7d6490bdfedbe92a9b07a714ca4f09a2426" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "futures", + "libp2p-identity", + "parity-scale-codec", + "prost-build 0.11.9", + "sc-consensus", + "sp-consensus", + "sp-consensus-grandpa", + "sp-runtime", +] [[package]] -name = "rustc_version" -version = "0.4.0" +name = "sc-network-gossip" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "27a9e8b4e16ab5b67b1fe389349855a2946d3c9168df54afcafec5dd67cae4cd" dependencies = [ - "semver", + "ahash 0.8.6", + "futures", + "futures-timer", + "libp2p", + "log", + "sc-network", + "sc-network-common", + "schnellru", + "sp-runtime", + "substrate-prometheus-endpoint", + "tracing", ] [[package]] -name = "rustify" -version = "0.5.3" +name = "sc-network-light" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c02e25271068de581e03ac3bb44db60165ff1a10d92b9530192ccb898bc706" +checksum = "e36fc98d43aa75eb0d0690af6a8c6a929318f6cb4bf1fc039410ece56c8bb5a9" dependencies = [ - "anyhow", - "async-trait", - "bytes", - "http", - "reqwest", - "rustify_derive", - "serde", - "serde_json", - "serde_urlencoded", + "array-bytes", + "async-channel", + "futures", + "libp2p-identity", + "log", + "parity-scale-codec", + "prost 0.11.9", + "prost-build 0.11.9", + "sc-client-api", + "sc-network", + "sp-blockchain", + "sp-core", + "sp-runtime", "thiserror", - "tracing", - "url", ] [[package]] -name = "rustify_derive" -version = "0.5.2" +name = "sc-network-sync" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58135536c18c04f4634bedad182a3f41baf33ef811cc38a3ec7b7061c57134c8" +checksum = "1d049b008a7353fc46cb45a1f6f68e5e5128442b6726cfd82da09cb676443e73" dependencies = [ - "proc-macro2", - "quote", - "regex", - "serde_urlencoded", - "syn 1.0.109", - "synstructure", + "array-bytes", + "async-channel", + "async-trait", + "fork-tree", + "futures", + "futures-timer", + "libp2p", + "log", + "mockall", + "parity-scale-codec", + "prost 0.11.9", + "prost-build 0.11.9", + "sc-client-api", + "sc-consensus", + "sc-network", + "sc-network-common", + "sc-utils", + "schnellru", + "smallvec", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-consensus-grandpa", + "sp-core", + "sp-runtime", + "substrate-prometheus-endpoint", + "thiserror", ] [[package]] -name = "rustix" -version = "0.36.15" +name = "sc-network-transactions" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" +checksum = "7ef6606f7705bc9c038c9e11715b7ddbdb2a5b43c12d8e3cc346e0b9927218e4" dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.45.0", + "array-bytes", + "futures", + "libp2p", + "log", + "parity-scale-codec", + "sc-network", + "sc-network-common", + "sc-utils", + "sp-consensus", + "sp-runtime", + "substrate-prometheus-endpoint", ] [[package]] -name = "rustix" -version = "0.37.24" +name = "sc-offchain" +version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4279d76516df406a8bd37e7dff53fd37d1a093f997a3c34a5c21658c126db06d" +checksum = "3b9d2458e033256bca62b01e89369bb9a7d74a460b74a5e3786afc5db3f55b1c" dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", + "array-bytes", + "bytes", + "fnv", + "futures", + "futures-timer", + "hyper", + "hyper-rustls", + "libp2p", + "log", + "num_cpus", + "once_cell", + "parity-scale-codec", + "parking_lot 0.12.1", + "rand 0.8.5", + "sc-client-api", + "sc-network", + "sc-network-common", + "sc-transaction-pool-api", + "sc-utils", + "sp-api", + "sp-core", + "sp-externalities", + "sp-keystore", + "sp-offchain", + "sp-runtime", + "threadpool", + "tracing", ] [[package]] -name = "rustix" -version = "0.38.17" +name = "sc-proposer-metrics" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" +checksum = "f6fc35c74a42a49d4ea4df44f78ebbd5a744f9bdca3f4ea1d3d9e5e02b0e6ee7" dependencies = [ - "bitflags 2.4.0", - "errno", - "libc", - "linux-raw-sys 0.4.8", - "windows-sys 0.48.0", + "log", + "substrate-prometheus-endpoint", ] [[package]] -name = "rustls" -version = "0.21.7" +name = "sc-rpc" +version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "ede50e654b3e0c076bb9beb041612af80f07dfb883cc05d8aaae1c7a1bb72761" dependencies = [ + "futures", + "jsonrpsee", "log", - "ring", - "rustls-webpki", - "sct", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-block-builder", + "sc-chain-spec", + "sc-client-api", + "sc-rpc-api", + "sc-tracing", + "sc-transaction-pool-api", + "sc-utils", + "serde_json", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-keystore", + "sp-offchain", + "sp-rpc", + "sp-runtime", + "sp-session", + "sp-statement-store", + "sp-version", + "tokio", ] [[package]] -name = "rustls-pemfile" -version = "1.0.3" +name = "sc-rpc-api" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1cac4149b7427beed423006c78e0b75c0193ac01d6e66ff0dd8a1909747cf593" dependencies = [ - "base64 0.21.4", + "jsonrpsee", + "parity-scale-codec", + "sc-chain-spec", + "sc-transaction-pool-api", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-version", + "thiserror", ] [[package]] -name = "rustls-webpki" -version = "0.101.6" +name = "sc-rpc-server" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +checksum = "9a62b9c5bf359cd4923ce10d294532936aa68d0cd59e890a0414f6434397180b" dependencies = [ - "ring", - "untrusted", + "http", + "jsonrpsee", + "log", + "serde_json", + "substrate-prometheus-endpoint", + "tokio", + "tower", + "tower-http", ] [[package]] -name = "rustversion" -version = "1.0.14" +name = "sc-rpc-spec-v2" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "2e770646ab839fb33dfeb7cbde94d98cdaf78526c70b10aa59ec5810953ff2a5" +dependencies = [ + "array-bytes", + "futures", + "futures-util", + "hex", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-chain-spec", + "sc-client-api", + "sc-transaction-pool-api", + "sc-utils", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "sp-version", + "thiserror", + "tokio", + "tokio-stream", +] [[package]] -name = "rusty-fork" -version = "0.3.0" +name = "sc-service" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +checksum = "b9c7fa14eaf48c44edff226ce9b18dc984c122e9deebbf825a8945be7c046ade" dependencies = [ - "fnv", - "quick-error", + "async-trait", + "directories", + "exit-future", + "futures", + "futures-timer", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "pin-project", + "rand 0.8.5", + "sc-block-builder", + "sc-chain-spec", + "sc-client-api", + "sc-client-db", + "sc-consensus", + "sc-executor", + "sc-informant", + "sc-keystore", + "sc-network", + "sc-network-bitswap", + "sc-network-common", + "sc-network-light", + "sc-network-sync", + "sc-network-transactions", + "sc-rpc", + "sc-rpc-server", + "sc-rpc-spec-v2", + "sc-sysinfo", + "sc-telemetry", + "sc-tracing", + "sc-transaction-pool", + "sc-transaction-pool-api", + "sc-utils", + "serde", + "serde_json", + "sp-api", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-externalities", + "sp-keystore", + "sp-runtime", + "sp-session", + "sp-state-machine", + "sp-storage", + "sp-transaction-pool", + "sp-transaction-storage-proof", + "sp-trie", + "sp-version", + "static_init", + "substrate-prometheus-endpoint", "tempfile", - "wait-timeout", + "thiserror", + "tokio", + "tracing", + "tracing-futures", ] [[package]] -name = "rvs_derive" -version = "0.3.2" +name = "sc-state-db" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1fa12378eb54f3d4f2db8dcdbe33af610b7e7d001961c1055858282ecef2a5" +checksum = "43bc9266fdec30b59857e794fc329aa600aaa6ed46799f9df859a7e30c0ec34b" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sp-core", ] [[package]] -name = "rvstruct" -version = "0.3.2" +name = "sc-statement-store" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5107860ec34506b64cf3680458074eac5c2c564f7ccc140918bbcd1714fd8d5d" +checksum = "d73800507e32d92f804acfa379dc6b6e91e0a2a25ce853b3848eb8aea019ea98" dependencies = [ - "rvs_derive", + "log", + "parity-db", + "parking_lot 0.12.1", + "sc-client-api", + "sc-keystore", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "sp-statement-store", + "substrate-prometheus-endpoint", + "tokio", ] [[package]] -name = "ryu" -version = "1.0.15" +name = "sc-sysinfo" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "4ff97437e564c0e7483d7e32384e3f6571f656728ea03a6e1b07a6325e064a76" +dependencies = [ + "futures", + "libc", + "log", + "rand 0.8.5", + "rand_pcg 0.3.1", + "regex", + "sc-telemetry", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-std", +] [[package]] -name = "ryu-js" -version = "0.2.2" +name = "sc-telemetry" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" +checksum = "4b46193a2979c86da75fc43276d222359757ea257b512fe6e4128e7a50b0bb22" +dependencies = [ + "chrono", + "futures", + "libp2p", + "log", + "parking_lot 0.12.1", + "pin-project", + "rand 0.8.5", + "sc-utils", + "serde", + "serde_json", + "thiserror", + "wasm-timer", +] [[package]] -name = "ryu_floating_decimal" -version = "0.1.0" +name = "sc-tracing" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "700de91d5fd6091442d00fdd9ee790af6d4f0f480562b0f5a1e8f59e90aafe73" +checksum = "4fcb4398268e83957ebbc84e6290307198e817caa47386135d3de6ba3316203a" +dependencies = [ + "ansi_term", + "atty", + "chrono", + "lazy_static", + "libc", + "log", + "parking_lot 0.12.1", + "regex", + "rustc-hash", + "sc-client-api", + "sc-tracing-proc-macro", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-tracing", + "thiserror", + "tracing", + "tracing-log", + "tracing-subscriber 0.2.25", +] [[package]] -name = "safe_arch" -version = "0.7.1" +name = "sc-tracing-proc-macro" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" +checksum = "71bd05d3f24c0c2489c57b90a76db883c23c25577718ca05c9b0181fd427f501" dependencies = [ - "bytemuck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.38", ] [[package]] -name = "same-file" -version = "1.0.6" +name = "sc-transaction-pool" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +checksum = "a6af477c0e8a2698aabf442a3918313e8f096eb6695ceaaa7e12679c496d2826" dependencies = [ - "winapi-util", + "async-trait", + "futures", + "futures-timer", + "linked-hash-map", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-client-api", + "sc-transaction-pool-api", + "sc-utils", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "sp-tracing", + "sp-transaction-pool", + "substrate-prometheus-endpoint", + "thiserror", ] [[package]] -name = "sawtooth-sdk" -version = "0.5.3" -source = "git+https://github.com/hyperledger/sawtooth-sdk-rust?rev=5a300de#5a300de293666aa056a2970a39651d4dd5098885" +name = "sc-transaction-pool-api" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f1b864d0ae8f1891eb310672c12fc160d24e37ef297d5ef0db257558fe13b1" dependencies = [ - "ctrlc", - "glob", - "hex", - "libc", + "async-trait", + "futures", "log", - "openssl", - "protobuf", - "protoc-rust", - "rand 0.8.5", - "secp256k1 0.27.0", - "sha2 0.10.8", - "uuid 0.8.2", - "zmq", + "parity-scale-codec", + "serde", + "sp-blockchain", + "sp-core", + "sp-runtime", + "thiserror", ] [[package]] -name = "sawtooth_tp" -version = "0.7.5" +name = "sc-utils" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b01c8eed623f999d402e44679d42ad42586afd4638aaed38708a307b59f4d7" dependencies = [ - "async-stl-client", - "async-trait", - "bytes", - "chronicle-protocol", - "chronicle-signing", - "chronicle-telemetry", - "chrono", - "clap 3.2.25", - "common", - "const_format", - "custom_error", - "derivative", + "async-channel", "futures", - "glob", - "hex", - "insta", + "futures-timer", "lazy_static", - "opa-tp-protocol", - "openssl", - "opentelemetry 0.19.0", - "prost 0.10.4", - "protobuf", - "rand 0.8.5", - "rand_core 0.6.4", - "sawtooth-sdk", - "serde", - "serde_derive", - "serde_json", - "tempfile", - "tokio", - "tracing", - "url", - "uuid 1.4.1", - "zmq", + "log", + "parking_lot 0.12.1", + "prometheus", + "sp-arithmetic", ] [[package]] name = "scale-info" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0a159d0c45c12b20c5a844feb1fe4bea86e28f17b92a5f0c42193634d3782" +checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" dependencies = [ "bitvec", "cfg-if", @@ -6254,9 +10096,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "912e55f6d20e0e80d63733872b40e1227c0bce1e1ab81ba67d696339bfd7fd29" +checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -6279,7 +10121,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" dependencies = [ - "parking_lot", + "parking_lot 0.12.1", ] [[package]] @@ -6288,7 +10130,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "cfg-if", "hashbrown 0.13.2", ] @@ -6317,14 +10159,42 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + [[package]] name = "sct" -version = "0.7.0" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "sct" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", +] + +[[package]] +name = "sdp" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d22a5ef407871893fd72b4562ee15e4742269b173959db4b8df6f538c414e13" +dependencies = [ + "rand 0.8.5", + "substring", + "thiserror", + "url", ] [[package]] @@ -6454,35 +10324,53 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.19" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -6501,9 +10389,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] @@ -6544,9 +10432,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.25" +version = "0.9.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" dependencies = [ "indexmap 2.0.2", "itoa", @@ -6565,6 +10453,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + [[package]] name = "sha1" version = "0.10.6" @@ -6640,6 +10541,12 @@ dependencies = [ "dirs", ] +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -6684,9 +10591,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" [[package]] name = "siphasher" @@ -6730,11 +10637,34 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +[[package]] +name = "snap" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" + +[[package]] +name = "snow" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155" +dependencies = [ + "aes-gcm 0.9.4", + "blake2", + "chacha20poly1305", + "curve25519-dalek 4.1.1", + "rand_core 0.6.4", + "ring 0.16.20", + "rustc_version", + "sha2 0.10.8", + "subtle", +] + [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -6742,14 +10672,31 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", ] +[[package]] +name = "soketto" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +dependencies = [ + "base64 0.13.1", + "bytes", + "flate2", + "futures", + "http", + "httparse", + "log", + "rand 0.8.5", + "sha-1", +] + [[package]] name = "sp-api" version = "22.0.0" @@ -6784,36 +10731,153 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", +] + +[[package]] +name = "sp-application-crypto" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74454c936a45ac55c8de95b9fd8b5e38f8b43d97df8f4274dd6777b20d95569" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-arithmetic" +version = "19.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e41f710a77e9debd1c9b80f862709dce648e50f0904cde4117488e7d11d4796d" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-std", + "static_assertions", +] + +[[package]] +name = "sp-block-builder" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c6a066e310d4c0c240829d7bb5d6bd01dde55d03e15b665f0372b40952f37e6" +dependencies = [ + "sp-api", + "sp-inherents", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-blockchain" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f506119858f25a73ed9d61a2ead0d5b97b5141055b3b4a12b9b82e530b06c673" +dependencies = [ + "futures", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "schnellru", + "sp-api", + "sp-consensus", + "sp-database", + "sp-runtime", + "sp-state-machine", + "thiserror", +] + +[[package]] +name = "sp-consensus" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e142e27f140d50701e613d925f61482fafccb7d90933ee30d7bae54d293ea3" +dependencies = [ + "async-trait", + "futures", + "log", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-state-machine", + "thiserror", +] + +[[package]] +name = "sp-consensus-aura" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dcc6df7a006a55651d0e7bdf2d8d4583d5b917cb4b7b6a1331398e96307a883" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-consensus-slots", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-babe" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572374a1260687fa18481ccac58c4a64611df379fb1aa65389ce96c6661b3b05" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", ] [[package]] -name = "sp-application-crypto" -version = "26.0.0" +name = "sp-consensus-grandpa" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74454c936a45ac55c8de95b9fd8b5e38f8b43d97df8f4274dd6777b20d95569" +checksum = "04d20516ed05a6a17f712050d6be385ca53c16b2d49938a29ca05e07f7aa5118" dependencies = [ + "finality-grandpa", + "log", "parity-scale-codec", "scale-info", "serde", + "sp-api", + "sp-application-crypto", "sp-core", - "sp-io", + "sp-keystore", + "sp-runtime", "sp-std", ] [[package]] -name = "sp-arithmetic" -version = "19.0.0" +name = "sp-consensus-slots" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e41f710a77e9debd1c9b80f862709dce648e50f0904cde4117488e7d11d4796d" +checksum = "9ebe1c46246a76af1105639c7434c1383d376fd45a8548fc18ed66dbf86f803c" dependencies = [ - "integer-sqrt", - "num-traits", "parity-scale-codec", "scale-info", "serde", "sp-std", - "static_assertions", + "sp-timestamp", ] [[package]] @@ -6826,7 +10890,7 @@ dependencies = [ "bitflags 1.3.2", "blake2", "bounded-collections", - "bs58", + "bs58 0.5.0", "dyn-clonable", "ed25519-zebra", "futures", @@ -6838,7 +10902,7 @@ dependencies = [ "log", "merlin", "parity-scale-codec", - "parking_lot", + "parking_lot 0.12.1", "paste", "primitive-types", "rand 0.8.5", @@ -6884,7 +10948,17 @@ checksum = "3a4327a220777a8d492ed3d0bcd4c769cbb030301e7d4a2d9e09513d690c313b" dependencies = [ "quote", "sp-core-hashing", - "syn 2.0.37", + "syn 2.0.38", +] + +[[package]] +name = "sp-database" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab25f79468af89010a8eb84e6bf56068b59929a55291c03519f47208360f3ebe" +dependencies = [ + "kvdb", + "parking_lot 0.12.1", ] [[package]] @@ -6895,7 +10969,7 @@ checksum = "16f7d375610590566e11882bf5b5a4b8d0666a96ba86808b2650bbbd9be50bf8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -6962,6 +11036,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "sp-keyring" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f09927534d2233e135e4b4a0c758554d0ff66178f6e9cfba2e151dfeac97b3" +dependencies = [ + "lazy_static", + "sp-core", + "sp-runtime", + "strum 0.24.1", +] + [[package]] name = "sp-keystore" version = "0.30.0" @@ -6969,12 +11055,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9f19e773319d96223ce8dba960267e6cb977907537a8f738746ceb86592413" dependencies = [ "parity-scale-codec", - "parking_lot", + "parking_lot 0.12.1", "sp-core", "sp-externalities", "thiserror", ] +[[package]] +name = "sp-maybe-compressed-blob" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "377a0e22a104a1a83804562fba6702537af6a36df9ee2049c89c3be9148b42b1" +dependencies = [ + "thiserror", + "zstd 0.12.4", +] + [[package]] name = "sp-metadata-ir" version = "0.3.0" @@ -6987,6 +11083,17 @@ dependencies = [ "sp-std", ] +[[package]] +name = "sp-offchain" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b1501eb4ede6471162ff48c85ccabb21434b698c8b61e2651f85c00bc1656f" +dependencies = [ + "sp-api", + "sp-core", + "sp-runtime", +] + [[package]] name = "sp-panic-handler" version = "11.0.0" @@ -6998,6 +11105,17 @@ dependencies = [ "regex", ] +[[package]] +name = "sp-rpc" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8534ae0a6043f70a93054bf0d3da27436637a8134ed44667c360e7a955cb3d" +dependencies = [ + "rustc-hash", + "serde", + "sp-core", +] + [[package]] name = "sp-runtime" version = "27.0.0" @@ -7050,7 +11168,23 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", +] + +[[package]] +name = "sp-session" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd062688577cc54493ba6f58383bfed89c66d5ef7b7c3747293b0da06c7f795" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-keystore", + "sp-runtime", + "sp-staking", + "sp-std", ] [[package]] @@ -7077,7 +11211,7 @@ dependencies = [ "hash-db", "log", "parity-scale-codec", - "parking_lot", + "parking_lot 0.12.1", "rand 0.8.5", "smallvec", "sp-core", @@ -7090,6 +11224,31 @@ dependencies = [ "trie-db", ] +[[package]] +name = "sp-statement-store" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11bbdc403457dd7a850078936aa7cc753c617b7bbeba5f5766ce5a55b2bf124" +dependencies = [ + "aes-gcm 0.10.3", + "curve25519-dalek 4.1.1", + "ed25519-dalek", + "hkdf", + "parity-scale-codec", + "rand 0.8.5", + "scale-info", + "sha2 0.10.8", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-externalities", + "sp-runtime", + "sp-runtime-interface", + "sp-std", + "thiserror", + "x25519-dalek 2.0.0", +] + [[package]] name = "sp-std" version = "11.0.0" @@ -7110,6 +11269,20 @@ dependencies = [ "sp-std", ] +[[package]] +name = "sp-timestamp" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0ab4b6b2d31db93e7da68894ccb7c5a305524cea051109820b958361d162be" +dependencies = [ + "async-trait", + "parity-scale-codec", + "sp-inherents", + "sp-runtime", + "sp-std", + "thiserror", +] + [[package]] name = "sp-tracing" version = "13.0.0" @@ -7123,20 +11296,46 @@ dependencies = [ "tracing-subscriber 0.2.25", ] +[[package]] +name = "sp-transaction-pool" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ea9c85f85f52e0a49c3f2ec6cff952fdc3ffe8392bebe21ed30eddd8d059c5" +dependencies = [ + "sp-api", + "sp-runtime", +] + +[[package]] +name = "sp-transaction-storage-proof" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a2d2d676a4c8e9ff18cb43782ed557d00de28ee9fb090842a8510e4a7ce0a7" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-trie", +] + [[package]] name = "sp-trie" version = "25.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bb2d292eb90452dcb0909fb44e74bf04395e3ffa37a66c0f1635a00600382a4" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "hash-db", "hashbrown 0.13.2", "lazy_static", "memory-db", "nohash-hasher", "parity-scale-codec", - "parking_lot", + "parking_lot 0.12.1", "scale-info", "schnellru", "sp-core", @@ -7174,7 +11373,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -7219,6 +11418,17 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spinners" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08615eea740067d9899969bc2891c68a19c315cb1f66640af9a9ecb91b13bcab" +dependencies = [ + "lazy_static", + "maplit", + "strum 0.24.1", +] + [[package]] name = "spki" version = "0.6.0" @@ -7230,74 +11440,243 @@ dependencies = [ ] [[package]] -name = "spki" -version = "0.7.2" +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der 0.7.8", +] + +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + +[[package]] +name = "ss58-registry" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6915280e2d0db8911e5032a5c275571af6bdded2916abd691a659be25d3439" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static-iref" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9786d4b8e9e5423fe85c57a826d7e0f0774746149a2ccd21e2104ff74b71ce7" +dependencies = [ + "iref", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "static_init" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" +dependencies = [ + "bitflags 1.3.2", + "cfg_aliases", + "libc", + "parking_lot 0.11.2", + "parking_lot_core 0.8.6", + "static_init_macro", + "winapi", +] + +[[package]] +name = "static_init_macro" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" +dependencies = [ + "cfg_aliases", + "memchr", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.38", +] + +[[package]] +name = "stun" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e94b1ec00bad60e6410e058b52f1c66de3dc5fe4d62d09b3e52bb7d3b73e25" +dependencies = [ + "base64 0.13.1", + "crc", + "lazy_static", + "md-5", + "rand 0.8.5", + "ring 0.16.20", + "subtle", + "thiserror", + "tokio", + "url", + "webrtc-util", +] + +[[package]] +name = "substrate-bip39" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "e620c7098893ba667438b47169c00aacdd9e7c10e042250ce2b60b087ec97328" dependencies = [ - "base64ct", - "der 0.7.8", + "hmac 0.11.0", + "pbkdf2 0.8.0", + "schnorrkel", + "sha2 0.9.9", + "zeroize", ] [[package]] -name = "sptr" -version = "0.3.2" +name = "substrate-build-script-utils" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" +checksum = "78127cdb5849eed7399ff9c730faea57c2a4e148e3b46e565abe98248432feb9" [[package]] -name = "ss58-registry" -version = "1.43.0" +name = "substrate-frame-rpc-system" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6915280e2d0db8911e5032a5c275571af6bdded2916abd691a659be25d3439" +checksum = "e5978d5bc95506e7770fe7de97610db13ee72dd1e242894d4843587dc5954102" dependencies = [ - "Inflector", - "num-format", - "proc-macro2", - "quote", - "serde", - "serde_json", - "unicode-xid", + "frame-system-rpc-runtime-api", + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "sc-rpc-api", + "sc-transaction-pool-api", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-core", + "sp-runtime", ] [[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static-iref" -version = "2.0.0" +name = "substrate-prometheus-endpoint" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9786d4b8e9e5423fe85c57a826d7e0f0774746149a2ccd21e2104ff74b71ce7" +checksum = "7e99fe4e955b8d7c25bd3a88a6907933867d11ef6194ef935e865a9e87c320ff" dependencies = [ - "iref", + "hyper", + "log", + "prometheus", + "thiserror", + "tokio", ] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "substrate-rpc-client" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "624257055386482adba21684a4af2cebdbaf0a8dd0e1b7cd9eec05b564afa5db" +dependencies = [ + "async-trait", + "jsonrpsee", + "log", + "sc-rpc-api", + "serde", + "sp-runtime", +] [[package]] -name = "strsim" -version = "0.10.0" +name = "substrate-wasm-builder" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "3a23975404eca6d81818f3f3d4ecde9635dae3e616f366dbc1a0d510c86f02a2" +dependencies = [ + "ansi_term", + "build-helper", + "cargo_metadata", + "filetime", + "parity-wasm", + "sp-maybe-compressed-blob", + "strum 0.24.1", + "tempfile", + "toml 0.7.8", + "walkdir", + "wasm-opt", +] [[package]] -name = "substrate-bip39" -version = "0.4.4" +name = "substring" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" dependencies = [ - "hmac 0.11.0", - "pbkdf2 0.8.0", - "schnorrkel", - "sha2 0.9.9", - "zeroize", + "autocfg", ] [[package]] @@ -7319,9 +11698,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -7386,20 +11765,20 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.11" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" +checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" [[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", - "fastrand", - "redox_syscall 0.3.5", - "rustix 0.38.17", + "fastrand 2.0.1", + "redox_syscall 0.4.1", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -7443,24 +11822,50 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "thiserror-impl-no-std" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "58e6318948b519ba6dc2b442a6d0b904ebfb8d411a3ad3e07843615a72249758" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 1.0.109", +] + +[[package]] +name = "thiserror-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ad459d94dd517257cc96add8a43190ee620011bb6e6cdc82dafd97dfafafea" +dependencies = [ + "thiserror-impl-no-std", ] +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + [[package]] name = "thread_local" version = "1.1.7" @@ -7471,13 +11876,34 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "time" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", + "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -7566,19 +11992,19 @@ dependencies = [ [[package]] name = "tokio" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", - "parking_lot", - "pin-project-lite", + "parking_lot 0.12.1", + "pin-project-lite 0.2.13", "signal-hook-registry", - "socket2 0.5.4", + "socket2 0.5.5", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -7590,7 +12016,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite", + "pin-project-lite 0.2.13", "tokio", ] @@ -7602,7 +12028,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -7615,13 +12041,24 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand 0.8.5", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.8", "tokio", ] @@ -7632,7 +12069,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", - "pin-project-lite", + "pin-project-lite 0.2.13", "tokio", "tokio-util", ] @@ -7651,15 +12088,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", - "pin-project-lite", + "pin-project-lite 0.2.13", "tokio", "tracing", ] @@ -7693,9 +12130,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] @@ -7721,7 +12158,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64 0.21.4", + "base64 0.21.5", "bytes", "futures-core", "futures-util", @@ -7751,7 +12188,7 @@ dependencies = [ "futures-util", "indexmap 1.9.3", "pin-project", - "pin-project-lite", + "pin-project-lite 0.2.13", "rand 0.8.5", "slab", "tokio", @@ -7761,6 +12198,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.4.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite 0.2.13", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -7775,33 +12230,32 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", - "pin-project-lite", + "pin-project-lite 0.2.13", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -7826,14 +12280,24 @@ dependencies = [ "version", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] @@ -7857,6 +12321,7 @@ dependencies = [ "chrono", "lazy_static", "matchers 0.0.1", + "parking_lot 0.11.2", "regex", "serde", "serde_json", @@ -7912,12 +12377,95 @@ dependencies = [ "hash-db", ] +[[package]] +name = "trust-dns-proto" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "rand 0.8.5", + "smallvec", + "socket2 0.4.10", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "lru-cache", + "parking_lot 0.12.1", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", + "trust-dns-proto", +] + [[package]] name = "try-lock" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "try-runtime-cli" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e30562bc5a4beccd52cc4091607830fa0b77c1ce09d55b47f232c8b3d03e23fa" +dependencies = [ + "async-trait", + "clap 4.4.7", + "frame-remote-externalities", + "frame-try-runtime", + "hex", + "log", + "parity-scale-codec", + "sc-cli", + "sc-executor", + "serde", + "serde_json", + "sp-api", + "sp-consensus-aura", + "sp-consensus-babe", + "sp-core", + "sp-debug-derive", + "sp-externalities", + "sp-inherents", + "sp-io", + "sp-keystore", + "sp-rpc", + "sp-runtime", + "sp-state-machine", + "sp-timestamp", + "sp-transaction-storage-proof", + "sp-version", + "sp-weights", + "substrate-rpc-client", + "zstd 0.12.4", +] + [[package]] name = "tt-call" version = "1.0.9" @@ -7943,6 +12491,25 @@ dependencies = [ "utf-8", ] +[[package]] +name = "turn" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4712ee30d123ec7ae26d1e1b218395a16c87cdbaf4b3925d170d684af62ea5e8" +dependencies = [ + "async-trait", + "base64 0.13.1", + "futures", + "log", + "md-5", + "rand 0.8.5", + "ring 0.16.20", + "stun", + "thiserror", + "tokio", + "webrtc-util", +] + [[package]] name = "twox-hash" version = "1.6.3" @@ -8015,23 +12582,67 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array 0.14.7", + "subtle", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "unsafe-libyaml" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +[[package]] +name = "unsigned-varint" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures-io", + "futures-util", +] + [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "uritemplate-next" @@ -8049,7 +12660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", - "idna", + "idna 0.4.0", "percent-encoding", "serde", ] @@ -8095,9 +12706,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom 0.2.10", "serde", @@ -8139,7 +12750,7 @@ checksum = "28084ac780b443e7f3514df984a2933bd3ab39e71914d951cdf8e4d298a7c9bc" dependencies = [ "async-trait", "bytes", - "derive_builder", + "derive_builder 0.12.0", "http", "reqwest", "rustify", @@ -8169,6 +12780,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -8178,6 +12795,21 @@ dependencies = [ "libc", ] +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "walkdir" version = "2.4.0" @@ -8230,7 +12862,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -8264,7 +12896,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8275,6 +12907,70 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasm-instrument" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa1dafb3e60065305741e83db35c6c2584bb3725b692b5b66148a38d72ace6cd" +dependencies = [ + "parity-wasm", +] + +[[package]] +name = "wasm-opt" +version = "0.114.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effbef3bd1dde18acb401f73e740a6f3d4a1bc651e9773bddc512fe4d8d68f67" +dependencies = [ + "anyhow", + "libc", + "strum 0.24.1", + "strum_macros 0.24.3", + "tempfile", + "thiserror", + "wasm-opt-cxx-sys", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-cxx-sys" +version = "0.114.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c09e24eb283919ace2ed5733bda4842a59ce4c8de110ef5c6d98859513d17047" +dependencies = [ + "anyhow", + "cxx", + "cxx-build", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-sys" +version = "0.114.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f2f817bed2e8d65eb779fa37317e74de15585751f903c9118342d1970703a4" +dependencies = [ + "anyhow", + "cc", + "cxx", + "cxx-build", +] + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.102.0" @@ -8292,7 +12988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" dependencies = [ "indexmap 1.9.3", - "semver", + "semver 1.0.20", ] [[package]] @@ -8311,9 +13007,12 @@ dependencies = [ "once_cell", "paste", "psm", + "rayon", "serde", "target-lexicon", "wasmparser 0.102.0", + "wasmtime-cache", + "wasmtime-cranelift 8.0.1", "wasmtime-environ 8.0.1", "wasmtime-jit 8.0.1", "wasmtime-runtime 8.0.1", @@ -8342,7 +13041,7 @@ dependencies = [ "serde_json", "target-lexicon", "wasmparser 0.107.0", - "wasmtime-cranelift", + "wasmtime-cranelift 10.0.2", "wasmtime-environ 10.0.2", "wasmtime-jit 10.0.2", "wasmtime-runtime 10.0.2", @@ -8367,6 +13066,48 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "wasmtime-cache" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86437fa68626fe896e5afc69234bb2b5894949083586535f200385adfd71213" +dependencies = [ + "anyhow", + "base64 0.21.5", + "bincode", + "directories-next", + "file-per-thread-logger", + "log", + "rustix 0.36.17", + "serde", + "sha2 0.10.8", + "toml 0.5.11", + "windows-sys 0.45.0", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "wasmtime-cranelift" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1cefde0cce8cb700b1b21b6298a3837dba46521affd7b8c38a9ee2c869eee04" +dependencies = [ + "anyhow", + "cranelift-codegen 0.95.1", + "cranelift-entity 0.95.1", + "cranelift-frontend 0.95.1", + "cranelift-native 0.95.1", + "cranelift-wasm 0.95.1", + "gimli 0.27.3", + "log", + "object 0.30.4", + "target-lexicon", + "thiserror", + "wasmparser 0.102.0", + "wasmtime-cranelift-shared 8.0.1", + "wasmtime-environ 8.0.1", +] + [[package]] name = "wasmtime-cranelift" version = "10.0.2" @@ -8374,22 +13115,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fc1e39ce9aa0fa0b319541ed423960b06cfa7343eca1574f811ea34275739c2" dependencies = [ "anyhow", - "cranelift-codegen", + "cranelift-codegen 0.97.2", "cranelift-control", "cranelift-entity 0.97.2", - "cranelift-frontend", - "cranelift-native", - "cranelift-wasm", + "cranelift-frontend 0.97.2", + "cranelift-native 0.97.2", + "cranelift-wasm 0.97.2", "gimli 0.27.3", "log", "object 0.30.4", "target-lexicon", "thiserror", "wasmparser 0.107.0", - "wasmtime-cranelift-shared", + "wasmtime-cranelift-shared 10.0.2", "wasmtime-environ 10.0.2", ] +[[package]] +name = "wasmtime-cranelift-shared" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd041e382ef5aea1b9fc78442394f1a4f6d676ce457e7076ca4cb3f397882f8b" +dependencies = [ + "anyhow", + "cranelift-codegen 0.95.1", + "cranelift-native 0.95.1", + "gimli 0.27.3", + "object 0.30.4", + "target-lexicon", + "wasmtime-environ 8.0.1", +] + [[package]] name = "wasmtime-cranelift-shared" version = "10.0.2" @@ -8397,9 +13153,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dd32739326690e51c76551d7cbf29d371e7de4dc7b37d2d503be314ab5b7d04" dependencies = [ "anyhow", - "cranelift-codegen", + "cranelift-codegen 0.97.2", "cranelift-control", - "cranelift-native", + "cranelift-native 0.97.2", "gimli 0.27.3", "object 0.30.4", "target-lexicon", @@ -8462,6 +13218,7 @@ dependencies = [ "serde", "target-lexicon", "wasmtime-environ 8.0.1", + "wasmtime-jit-debug 8.0.1", "wasmtime-jit-icache-coherence 8.0.1", "wasmtime-runtime 8.0.1", "windows-sys 0.45.0", @@ -8482,7 +13239,7 @@ dependencies = [ "log", "object 0.30.4", "rustc-demangle", - "rustix 0.37.24", + "rustix 0.37.27", "serde", "target-lexicon", "wasmtime-environ 10.0.2", @@ -8496,130 +13253,369 @@ name = "wasmtime-jit-debug" version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e0554b84c15a27d76281d06838aed94e13a77d7bf604bbbaf548aa20eb93846" +dependencies = [ + "object 0.30.4", + "once_cell", + "rustix 0.36.17", +] + +[[package]] +name = "wasmtime-jit-debug" +version = "10.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46b7e98979a69d3df093076bde8431204e3c96a770e8d216fea365c627d88a4" dependencies = [ "once_cell", ] [[package]] -name = "wasmtime-jit-debug" -version = "10.0.2" +name = "wasmtime-jit-icache-coherence" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecae978b13f7f67efb23bd827373ace4578f2137ec110bbf6a4a7cde4121bbd" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "10.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb1e7c68ede63dc7a98c3e473162954e224951854e229c8b4e74697fe17dbdd" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "wasmtime-runtime" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658cf6f325232b6760e202e5255d823da5e348fdea827eff0a2a22319000b441" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "indexmap 1.9.3", + "libc", + "log", + "mach", + "memfd", + "memoffset 0.8.0", + "paste", + "rand 0.8.5", + "rustix 0.36.17", + "wasmtime-asm-macros 8.0.1", + "wasmtime-environ 8.0.1", + "wasmtime-jit-debug 8.0.1", + "windows-sys 0.45.0", +] + +[[package]] +name = "wasmtime-runtime" +version = "10.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843e33bf9e0f0c57902c87a1dea1389cc23865c65f007214318dbdfcb3fd4ae5" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "indexmap 1.9.3", + "libc", + "log", + "mach", + "memfd", + "memoffset 0.8.0", + "paste", + "rand 0.8.5", + "rustix 0.37.27", + "sptr", + "wasmtime-asm-macros 10.0.2", + "wasmtime-environ 10.0.2", + "wasmtime-jit-debug 10.0.2", + "windows-sys 0.48.0", +] + +[[package]] +name = "wasmtime-types" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" +dependencies = [ + "cranelift-entity 0.95.1", + "serde", + "thiserror", + "wasmparser 0.102.0", +] + +[[package]] +name = "wasmtime-types" +version = "10.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7473a07bebd85671bada453123e3d465c8e0a59668ff79f5004076e6a2235ef5" +dependencies = [ + "cranelift-entity 0.97.2", + "serde", + "thiserror", + "wasmparser 0.107.0", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.5", + "untrusted 0.9.0", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki 0.22.4", +] + +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + +[[package]] +name = "webrtc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3bc9049bdb2cea52f5fd4f6f728184225bdb867ed0dc2410eab6df5bdd67bb" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "hex", + "interceptor", + "lazy_static", + "log", + "rand 0.8.5", + "rcgen 0.9.3", + "regex", + "ring 0.16.20", + "rtcp", + "rtp", + "rustls 0.19.1", + "sdp", + "serde", + "serde_json", + "sha2 0.10.8", + "stun", + "thiserror", + "time", + "tokio", + "turn", + "url", + "waitgroup", + "webrtc-data", + "webrtc-dtls", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46b7e98979a69d3df093076bde8431204e3c96a770e8d216fea365c627d88a4" +checksum = "0ef36a4d12baa6e842582fe9ec16a57184ba35e1a09308307b67d43ec8883100" dependencies = [ - "once_cell", + "bytes", + "derive_builder 0.11.2", + "log", + "thiserror", + "tokio", + "webrtc-sctp", + "webrtc-util", ] [[package]] -name = "wasmtime-jit-icache-coherence" -version = "8.0.1" +name = "webrtc-dtls" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecae978b13f7f67efb23bd827373ace4578f2137ec110bbf6a4a7cde4121bbd" +checksum = "c4a00f4242f2db33307347bd5be53263c52a0331c96c14292118c9a6bb48d267" dependencies = [ - "cfg-if", - "libc", - "windows-sys 0.45.0", + "aes 0.6.0", + "aes-gcm 0.10.3", + "async-trait", + "bincode", + "block-modes", + "byteorder", + "ccm", + "curve25519-dalek 3.2.0", + "der-parser 8.2.0", + "elliptic-curve 0.12.3", + "hkdf", + "hmac 0.12.1", + "log", + "p256", + "p384", + "rand 0.8.5", + "rand_core 0.6.4", + "rcgen 0.10.0", + "ring 0.16.20", + "rustls 0.19.1", + "sec1 0.3.0", + "serde", + "sha1", + "sha2 0.10.8", + "signature 1.6.4", + "subtle", + "thiserror", + "tokio", + "webpki 0.21.4", + "webrtc-util", + "x25519-dalek 2.0.0", + "x509-parser 0.13.2", ] [[package]] -name = "wasmtime-jit-icache-coherence" -version = "10.0.2" +name = "webrtc-ice" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb1e7c68ede63dc7a98c3e473162954e224951854e229c8b4e74697fe17dbdd" +checksum = "465a03cc11e9a7d7b4f9f99870558fe37a102b65b93f8045392fef7c67b39e80" dependencies = [ - "cfg-if", - "libc", - "windows-sys 0.48.0", + "arc-swap", + "async-trait", + "crc", + "log", + "rand 0.8.5", + "serde", + "serde_json", + "stun", + "thiserror", + "tokio", + "turn", + "url", + "uuid 1.5.0", + "waitgroup", + "webrtc-mdns", + "webrtc-util", ] [[package]] -name = "wasmtime-runtime" -version = "8.0.1" +name = "webrtc-mdns" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658cf6f325232b6760e202e5255d823da5e348fdea827eff0a2a22319000b441" +checksum = "f08dfd7a6e3987e255c4dbe710dde5d94d0f0574f8a21afa95d171376c143106" dependencies = [ - "anyhow", - "cc", - "cfg-if", - "indexmap 1.9.3", - "libc", "log", - "mach", - "memfd", - "memoffset 0.8.0", - "paste", - "rand 0.8.5", - "rustix 0.36.15", - "wasmtime-asm-macros 8.0.1", - "wasmtime-environ 8.0.1", - "wasmtime-jit-debug 8.0.1", - "windows-sys 0.45.0", + "socket2 0.4.10", + "thiserror", + "tokio", + "webrtc-util", ] [[package]] -name = "wasmtime-runtime" -version = "10.0.2" +name = "webrtc-media" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843e33bf9e0f0c57902c87a1dea1389cc23865c65f007214318dbdfcb3fd4ae5" +checksum = "f72e1650a8ae006017d1a5280efb49e2610c19ccc3c0905b03b648aee9554991" dependencies = [ - "anyhow", - "cc", - "cfg-if", - "indexmap 1.9.3", - "libc", - "log", - "mach", - "memfd", - "memoffset 0.8.0", - "paste", + "byteorder", + "bytes", "rand 0.8.5", - "rustix 0.37.24", - "sptr", - "wasmtime-asm-macros 10.0.2", - "wasmtime-environ 10.0.2", - "wasmtime-jit-debug 10.0.2", - "windows-sys 0.48.0", + "rtp", + "thiserror", ] [[package]] -name = "wasmtime-types" -version = "8.0.1" +name = "webrtc-sctp" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" +checksum = "0d47adcd9427eb3ede33d5a7f3424038f63c965491beafcc20bc650a2f6679c0" dependencies = [ - "cranelift-entity 0.95.1", - "serde", + "arc-swap", + "async-trait", + "bytes", + "crc", + "log", + "rand 0.8.5", "thiserror", - "wasmparser 0.102.0", + "tokio", + "webrtc-util", ] [[package]] -name = "wasmtime-types" -version = "10.0.2" +name = "webrtc-srtp" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7473a07bebd85671bada453123e3d465c8e0a59668ff79f5004076e6a2235ef5" +checksum = "6183edc4c1c6c0175f8812eefdce84dfa0aea9c3ece71c2bf6ddd3c964de3da5" dependencies = [ - "cranelift-entity 0.97.2", - "serde", + "aead 0.4.3", + "aes 0.7.5", + "aes-gcm 0.9.4", + "async-trait", + "byteorder", + "bytes", + "ctr 0.8.0", + "hmac 0.11.0", + "log", + "rtcp", + "rtp", + "sha-1", + "subtle", "thiserror", - "wasmparser 0.107.0", + "tokio", + "webrtc-util", ] [[package]] -name = "web-sys" -version = "0.3.64" +name = "webrtc-util" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "93f1db1727772c05cf7a2cfece52c3aca8045ca1e176cd517d323489aa3c6d87" dependencies = [ - "js-sys", - "wasm-bindgen", + "async-trait", + "bitflags 1.3.2", + "bytes", + "cc", + "ipnet", + "lazy_static", + "libc", + "log", + "nix 0.24.3", + "rand 0.8.5", + "thiserror", + "tokio", + "winapi", ] -[[package]] -name = "webpki-roots" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" - [[package]] name = "which" version = "4.4.2" @@ -8629,19 +13625,25 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.17", + "rustix 0.38.21", ] [[package]] name = "wide" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebecebefc38ff1860b4bc47550bbfa63af5746061cf0d29fcd7fa63171602598" +checksum = "c68938b57b33da363195412cfc5fc37c9ed49aa9cfe2156fde64b8d2c9498242" dependencies = [ "bytemuck", "safe_arch", ] +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" @@ -8675,9 +13677,19 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.48.0" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets 0.48.5", ] @@ -8816,9 +13828,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "176b6138793677221d420fd2f0aeeced263f197688b36484660da767bca2fa32" dependencies = [ "memchr", ] @@ -8842,6 +13854,66 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" +dependencies = [ + "curve25519-dalek 3.2.0", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" +dependencies = [ + "curve25519-dalek 4.1.1", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c" +dependencies = [ + "asn1-rs 0.3.1", + "base64 0.13.1", + "data-encoding", + "der-parser 7.0.0", + "lazy_static", + "nom", + "oid-registry 0.4.0", + "ring 0.16.20", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" +dependencies = [ + "asn1-rs 0.5.2", + "base64 0.13.1", + "data-encoding", + "der-parser 8.2.0", + "lazy_static", + "nom", + "oid-registry 0.6.1", + "rusticata-macros", + "thiserror", + "time", +] + [[package]] name = "xattr" version = "1.0.1" @@ -8860,12 +13932,55 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yamux" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot 0.12.1", + "rand 0.8.5", + "static_assertions", +] + [[package]] name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "zerocopy" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd66a62464e3ffd4e37bd09950c2b9dd6c4f8767380fabba0d523f9a775bc85a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "255c4596d41e6916ced49cfafea18727b24d67878fa180ddfd69b9df34fd1726" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "zeroize" version = "1.6.0" @@ -8883,7 +13998,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -8917,3 +14032,51 @@ dependencies = [ "metadeps", "zeromq-src", ] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" +dependencies = [ + "zstd-safe 6.0.6", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-safe" +version = "6.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 3da7ee5e2..3339dacdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,9 @@ members = [ "crates/opa-tp-protocol", "crates/opactl", "crates/sawtooth-tp", - "crates/frame-chronicle" + "crates/pallet-chronicle", + "node/runtime-chronicle", + "node/node-chronicle", ] [workspace.dependencies] @@ -67,7 +69,6 @@ hex = "0.4.3" http = "0.2.9" insta = { version = "1.26.0", features = ["redactions", "toml"] } iref = "2.2" -iref-enum = "2.1" is-terminal = "0.4" json-ld = { version = "0.14" } json-syntax = { version = "0.9", features = ["serde", "serde_json"] } @@ -134,20 +135,12 @@ serde_derive = "1.0.152" serde_json = "1.0.93" serde_yaml = "0.9.14" shellexpand = "3.0.0" -static-iref = "2.0.0" -sp-runtime = "27.0.0" -sp-core= "24.0.0" -sp-std = "11.0.0" -sp-io = "26.0.0" -macro-attr-2018 = "0.3.0" +macro-attr-2018 = "3.0.0" newtype-derive-2018 = "0.2.1" temp-dir = "0.1.11" tempfile = "3.4.0" -testcontainers = "0.14" thiserror = "1.0" tmq = "0.3" -scale-info = "2.9.0" -parity-scale-codec = {version = "3.6.1", features=["std"]} tokio = { version = "1.27", features = [ "time", "macros", @@ -173,3 +166,4 @@ uuid = "1.2.2" valico = "3.6.0" vaultrs = "*" zmq = { version = "0.9", features = ["vendored"] } +testcontainers = "0.14" diff --git a/LICENSE b/LICENSE index 261eeb9e9..ffa0b3f2d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,16 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +MIT No Attribution + +Copyright Parity Technologies (UK) Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index bc4715f56..337facaaf 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,164 @@ -# Chronicle +# Substrate Node Template -A tool for recording and querying provenance data stored on distributed ledgers. +A fresh [Substrate](https://substrate.io/) node, ready for hacking :rocket: + +A standalone version of this template is available for each release of Polkadot in the [Substrate Developer Hub Parachain Template](https://github.com/substrate-developer-hub/substrate-parachain-template/) repository. +The parachain template is generated directly at each Polkadot release branch from the [Node Template in Substrate](https://github.com/paritytech/substrate/tree/master/bin/node-template) upstream + +It is usually best to use the stand-alone version to start a new project. +All bugs, suggestions, and feature requests should be made upstream in the [Substrate](https://github.com/paritytech/substrate/tree/master/bin/node-template) repository. ## Getting Started -We recommend you start by exploring the Chronicle examples repo, and trying -to build and experiment with one of the runnable application domains there: +Depending on your operating system and Rust version, there might be additional packages required to compile this template. +Check the [Install](https://docs.substrate.io/install/) instructions for your platform for the most common dependencies. +Alternatively, you can use one of the [alternative installation](#alternatives-installations) options. + +### Build + +Use the following command to build the node without launching it: + +```sh +cargo build --release +``` + +### Embedded Docs + +After you build the project, you can use the following command to explore its parameters and subcommands: + +```sh +./target/release/node-template -h +``` + +You can generate and view the [Rust Docs](https://doc.rust-lang.org/cargo/commands/cargo-doc.html) for this template with this command: + +```sh +cargo +nightly doc --open +``` + +### Single-Node Development Chain + +The following command starts a single-node development chain that doesn't persist state: + +```sh +./target/release/node-template --dev +``` + +To purge the development chain's state, run the following command: + +```sh +./target/release/node-template purge-chain --dev +``` + +To start the development chain with detailed logging, run the following command: + +```sh +RUST_BACKTRACE=1 ./target/release/node-template -ldebug --dev +``` + +Development chains: + +- Maintain state in a `tmp` folder while the node is running. +- Use the **Alice** and **Bob** accounts as default validator authorities. +- Use the **Alice** account as the default `sudo` account. +- Are preconfigured with a genesis state (`/node/src/chain_spec.rs`) that includes several prefunded development accounts. + + +To persist chain state between runs, specify a base path by running a command similar to the following: + +```sh +// Create a folder to use as the db base path +$ mkdir my-chain-state + +// Use of that folder to store the chain state +$ ./target/release/node-template --dev --base-path ./my-chain-state/ + +// Check the folder structure created inside the base path after running the chain +$ ls ./my-chain-state +chains +$ ls ./my-chain-state/chains/ +dev +$ ls ./my-chain-state/chains/dev +db keystore network +``` + +### Connect with Polkadot-JS Apps Front-End + +After you start the node template locally, you can interact with it using the hosted version of the [Polkadot/Substrate Portal](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) front-end by connecting to the local node endpoint. +A hosted version is also available on [IPFS (redirect) here](https://dotapps.io/) or [IPNS (direct) here](ipns://dotapps.io/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/explorer). +You can also find the source code and instructions for hosting your own instance on the [polkadot-js/apps](https://github.com/polkadot-js/apps) repository. + +### Multi-Node Local Testnet + +If you want to see the multi-node consensus algorithm in action, see [Simulate a network](https://docs.substrate.io/tutorials/build-a-blockchain/simulate-network/). + +## Template Structure + +A Substrate project such as this consists of a number of components that are spread across a few directories. + +### Node + +A blockchain node is an application that allows users to participate in a blockchain network. +Substrate-based blockchain nodes expose a number of capabilities: -## Documentation +- Networking: Substrate nodes use the [`libp2p`](https://libp2p.io/) networking stack to allow the + nodes in the network to communicate with one another. +- Consensus: Blockchains must have a way to come to [consensus](https://docs.substrate.io/fundamentals/consensus/) on the state of the network. + Substrate makes it possible to supply custom consensus engines and also ships with several consensus mechanisms that have been built on top of [Web3 Foundation research](https://research.web3.foundation/en/latest/polkadot/NPoS/index.html). +- RPC Server: A remote procedure call (RPC) server is used to interact with Substrate nodes. -- Examples: -- Chronicle: +There are several files in the `node` directory. +Take special note of the following: -## Provenance +- [`chain_spec.rs`](./node/src/chain_spec.rs): A [chain specification](https://docs.substrate.io/build/chain-spec/) is a source code file that defines a Substrate chain's initial (genesis) state. + Chain specifications are useful for development and testing, and critical when architecting the launch of a production chain. + Take note of the `development_config` and `testnet_genesis` functions,. + These functions are used to define the genesis state for the local development chain configuration. + These functions identify some [well-known accounts](https://docs.substrate.io/reference/command-line-tools/subkey/) and use them to configure the blockchain's initial state. +- [`service.rs`](./node/src/service.rs): This file defines the node implementation. + Take note of the libraries that this file imports and the names of the functions it invokes. + In particular, there are references to consensus-related topics, such as the [block finalization and forks](https://docs.substrate.io/fundamentals/consensus/#finalization-and-forks) and other [consensus mechanisms](https://docs.substrate.io/fundamentals/consensus/#default-consensus-models) such as Aura for block authoring and GRANDPA for finality. -Chronicle is built on W3C open standards: -- [PROV-O - The PROV Ontology](https://www.w3.org/TR/prov-o/) -- [JSON-LD](https://www.w3.org/TR/json-ld11/) -Chronicle implements the PROV-O -[starting point terms](https://www.w3.org/TR/2013/REC-prov-o-20130430/#description-starting-point-terms) -shown below, encoding them using the JSON-LD -[compaction algorithm](https://json-ld.org/spec/latest/json-ld-api/#compaction) -onto a backend ledger - currently Hyperledger Sawtooth - or an in-memory stub -for testing purposes. +### Runtime -![PROV-O](https://www.w3.org/TR/prov-o/diagrams/starting-points.svg) +In Substrate, the terms "runtime" and "state transition function" are analogous. +Both terms refer to the core logic of the blockchain that is responsible for validating blocks and executing the state changes they define. +The Substrate project in this repository uses [FRAME](https://docs.substrate.io/learn/runtime-development/#frame) to construct a blockchain runtime. +FRAME allows runtime developers to declare domain-specific logic in modules called "pallets". +At the heart of FRAME is a helpful [macro language](https://docs.substrate.io/reference/frame-macros/) that makes it easy to create pallets and flexibly compose them to create blockchains that can address [a variety of needs](https://substrate.io/ecosystem/projects/). -Chronicle extends the core PROV-O vocabulary as described -[here](docs/chronicle_vocabulary.md). +Review the [FRAME runtime implementation](./runtime/src/lib.rs) included in this template and note the following: -## Deployment +- This file configures several pallets to include in the runtime. + Each pallet configuration is defined by a code block that begins with `impl $PALLET_NAME::Config for Runtime`. +- The pallets are composed into a single runtime by way of the [`construct_runtime!`](https://paritytech.github.io/substrate/master/frame_support/macro.construct_runtime.html) macro, which is part of the [core FRAME pallet library](https://docs.substrate.io/reference/frame-pallets/#system-pallets). -Chronicle is a self contained binary executable that can be used as an ephemeral -command line interface for provenance recording and interrogation or as a -GraphQL server to provide an interface for higher level services. It embeds -PostgreSQL for local synchronization with a backend ledger and is capable of -basic key management using a file system. +### Pallets -Chronicle instances have individual data stores, so they do not share state -directly and synchronize via ledger updates. The abstract transaction processor -should process an API operation in under a millisecond. +The runtime in this project is constructed using many FRAME pallets that ship with [the Substrate repository](https://github.com/paritytech/substrate/tree/master/frame) and a template pallet that is [defined in the `pallets`](./pallets/template/src/lib.rs) directory. -## Transaction Processing +A FRAME pallet is comprised of a number of blockchain primitives, including: -Chronicle records provenance by running a deterministic transaction -processor both locally and as part of consensus. Local execution ensures that -duplicated provenance will not waste resources in consensus and that most -contradictions will also be caught early. Provenance will be sent to a validator -and recorded on chain unless it contradicts previous provenance. +- Storage: FRAME defines a rich set of powerful [storage abstractions](https://docs.substrate.io/build/runtime-storage/) that makes it easy to use Substrate's efficient key-value database to manage the evolving state of a blockchain. +- Dispatchables: FRAME pallets define special types of functions that can be invoked (dispatched) from outside of the runtime in order to update its state. +- Events: Substrate uses [events](https://docs.substrate.io/build/events-and-errors/) to notify users of significant state changes. +- Errors: When a dispatchable fails, it returns an error. -## Call For Open-Source Contributions +Each pallet has its own `Config` trait which serves as a configuration interface to generically define the types and parameters it depends on. -You can read all about our rationale for open sourcing Chronicle on Medium where -our Chief Strategy Officer, Csilla Zsigri, published this article -[Chronicle: You Say Provenance, We Say Open Source](https://medium.com/btpworks/chronicle-you-say-provenance-we-say-open-source-737c506dc9c0). +## Alternatives Installations -We will be publishing a roadmap shortly at which point all developers are -invited to contribute to our efforts to make assets trustworthy. +Instead of installing dependencies and building this source directly, consider the following alternatives. -You can participate in the following ways: +### Nix -1. Join our [Slack group](https://communityinviter.com/apps/chronicleworks/joinus) - to chat -1. Submit an issue or PR on GitHub +Install [nix](https://nixos.org/) and +[nix-direnv](https://github.com/nix-community/nix-direnv) for a fully plug-and-play +experience for setting up the development environment. +To get all the correct dependencies, activate direnv `direnv allow`. -## License +### Docker -See the [LICENSE](LICENSE) file. +Please follow the [Substrate Docker instructions here](https://github.com/paritytech/substrate/blob/master/docker/README.md) to build the Docker container with the Substrate Node Template binary. diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index 524726db3..c0295433b 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -33,7 +33,6 @@ futures = { workspace = true } glob = { workspace = true } hex = { workspace = true } iref = { workspace = true } -iref-enum = { workspace = true } json-ld = { workspace = true } jwtk = { workspace = true } lazy_static = { workspace = true } @@ -55,7 +54,6 @@ sawtooth_tp = { path = "../sawtooth-tp" } serde = { workspace = true } serde_derive = { workspace = true } serde_json = { workspace = true } -static-iref = { workspace = true } thiserror = { workspace = true } tmq = { workspace = true } tokio = { workspace = true } diff --git a/crates/api/src/chronicle_graphql/activity.rs b/crates/api/src/chronicle_graphql/activity.rs index 41fb75acb..5389c6989 100644 --- a/crates/api/src/chronicle_graphql/activity.rs +++ b/crates/api/src/chronicle_graphql/activity.rs @@ -5,181 +5,166 @@ use diesel::prelude::*; use std::collections::HashMap; pub async fn namespace<'a>( - namespaceid: i32, - ctx: &Context<'a>, + namespaceid: i32, + ctx: &Context<'a>, ) -> async_graphql::Result { - use crate::persistence::schema::namespace::{self, dsl}; - let store = ctx.data_unchecked::(); + use crate::persistence::schema::namespace::{self, dsl}; + let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; + let mut connection = store.pool.get()?; - Ok(namespace::table - .filter(dsl::id.eq(namespaceid)) - .first::(&mut connection)?) + Ok(namespace::table + .filter(dsl::id.eq(namespaceid)) + .first::(&mut connection)?) } pub async fn was_associated_with<'a>( - id: i32, - ctx: &Context<'a>, + id: i32, + ctx: &Context<'a>, ) -> async_graphql::Result, Option, Option)>> { - use crate::persistence::schema::{agent, association, delegation}; - - #[derive(Queryable)] - struct DelegationAgents { - responsible_id: i32, - delegate: Agent, - role: String, - } - - let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; - - let delegation_entries = delegation::table - .filter(delegation::dsl::activity_id.eq(id)) - .inner_join(agent::table.on(agent::id.eq(delegation::delegate_id))) - .select(( - delegation::responsible_id, - Agent::as_select(), - delegation::role, - )) - .load::(&mut connection)? - .into_iter(); - - let mut agent_reservoir = HashMap::new(); - let mut agent_delegations = HashMap::new(); - - for delegation_entry in delegation_entries { - let delegate_id = delegation_entry.delegate.id; - agent_reservoir.insert(delegate_id, delegation_entry.delegate); - agent_delegations.insert( - delegation_entry.responsible_id, - ( - delegate_id, - if delegation_entry.role.is_empty() { - None - } else { - Some(Role(delegation_entry.role)) - }, - ), - ); - } - - let res = association::table - .filter(association::dsl::activity_id.eq(id)) - .inner_join(crate::persistence::schema::agent::table) - .order(crate::persistence::schema::agent::external_id) - .select((Agent::as_select(), association::role)) - .load::<(Agent, Role)>(&mut connection)? - .into_iter() - .map(|(responsible_agent, responsible_role)| { - let responsible_role = if responsible_role.0.is_empty() { - None - } else { - Some(responsible_role) - }; - let (delegate_agent, delegate_role): (Option, Option) = - match agent_delegations.get(&responsible_agent.id) { - Some((delegate_id, optional_role)) => { - let delegate = agent_reservoir.remove(delegate_id).unwrap_or_else(|| { - agent::table - .find(delegate_id) - .first::(&mut connection) - .unwrap() - }); - let optional_role = optional_role.as_ref().cloned(); - (Some(delegate), optional_role) - } - None => (None, None), - }; - ( - responsible_agent, - responsible_role, - delegate_agent, - delegate_role, - ) - }) - .collect(); - - Ok(res) + use crate::persistence::schema::{agent, association, delegation}; + + #[derive(Queryable)] + struct DelegationAgents { + responsible_id: i32, + delegate: Agent, + role: String, + } + + let store = ctx.data_unchecked::(); + let mut connection = store.pool.get()?; + + let delegation_entries = delegation::table + .filter(delegation::dsl::activity_id.eq(id)) + .inner_join(agent::table.on(agent::id.eq(delegation::delegate_id))) + .select((delegation::responsible_id, Agent::as_select(), delegation::role)) + .load::(&mut connection)? + .into_iter(); + + let mut agent_reservoir = HashMap::new(); + let mut agent_delegations = HashMap::new(); + + for delegation_entry in delegation_entries { + let delegate_id = delegation_entry.delegate.id; + agent_reservoir.insert(delegate_id, delegation_entry.delegate); + agent_delegations.insert( + delegation_entry.responsible_id, + ( + delegate_id, + if delegation_entry.role.is_empty() { + None + } else { + Some(Role(delegation_entry.role)) + }, + ), + ); + } + + let res = association::table + .filter(association::dsl::activity_id.eq(id)) + .inner_join(crate::persistence::schema::agent::table) + .order(crate::persistence::schema::agent::external_id) + .select((Agent::as_select(), association::role)) + .load::<(Agent, Role)>(&mut connection)? + .into_iter() + .map(|(responsible_agent, responsible_role)| { + let responsible_role = + if responsible_role.0.is_empty() { None } else { Some(responsible_role) }; + let (delegate_agent, delegate_role): (Option, Option) = + match agent_delegations.get(&responsible_agent.id) { + Some((delegate_id, optional_role)) => { + let delegate = agent_reservoir.remove(delegate_id).unwrap_or_else(|| { + agent::table.find(delegate_id).first::(&mut connection).unwrap() + }); + let optional_role = optional_role.as_ref().cloned(); + (Some(delegate), optional_role) + }, + None => (None, None), + }; + (responsible_agent, responsible_role, delegate_agent, delegate_role) + }) + .collect(); + + Ok(res) } pub async fn used<'a>(id: i32, ctx: &Context<'a>) -> async_graphql::Result> { - use crate::persistence::schema::usage::{self, dsl}; + use crate::persistence::schema::usage::{self, dsl}; - let store = ctx.data_unchecked::(); + let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; + let mut connection = store.pool.get()?; - let res = usage::table - .filter(dsl::activity_id.eq(id)) - .inner_join(crate::persistence::schema::entity::table) - .order(crate::persistence::schema::entity::external_id) - .select(Entity::as_select()) - .load::(&mut connection)?; + let res = usage::table + .filter(dsl::activity_id.eq(id)) + .inner_join(crate::persistence::schema::entity::table) + .order(crate::persistence::schema::entity::external_id) + .select(Entity::as_select()) + .load::(&mut connection)?; - Ok(res) + Ok(res) } pub async fn was_informed_by<'a>( - id: i32, - ctx: &Context<'a>, + id: i32, + ctx: &Context<'a>, ) -> async_graphql::Result> { - use crate::persistence::schema::wasinformedby::{self, dsl}; + use crate::persistence::schema::wasinformedby::{self, dsl}; - let store = ctx.data_unchecked::(); + let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; + let mut connection = store.pool.get()?; - let res = - wasinformedby::table - .filter(dsl::activity_id.eq(id)) - .inner_join(crate::persistence::schema::activity::table.on( - wasinformedby::informing_activity_id.eq(crate::persistence::schema::activity::id), - )) - .order(crate::persistence::schema::activity::external_id) - .select(Activity::as_select()) - .load::(&mut connection)?; + let res = + wasinformedby::table + .filter(dsl::activity_id.eq(id)) + .inner_join(crate::persistence::schema::activity::table.on( + wasinformedby::informing_activity_id.eq(crate::persistence::schema::activity::id), + )) + .order(crate::persistence::schema::activity::external_id) + .select(Activity::as_select()) + .load::(&mut connection)?; - Ok(res) + Ok(res) } pub async fn generated<'a>(id: i32, ctx: &Context<'a>) -> async_graphql::Result> { - use crate::persistence::schema::generation::{self, dsl}; + use crate::persistence::schema::generation::{self, dsl}; - let store = ctx.data_unchecked::(); + let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; + let mut connection = store.pool.get()?; - let res = generation::table - .filter(dsl::activity_id.eq(id)) - .inner_join(crate::persistence::schema::entity::table) - .select(Entity::as_select()) - .load::(&mut connection)?; + let res = generation::table + .filter(dsl::activity_id.eq(id)) + .inner_join(crate::persistence::schema::entity::table) + .select(Entity::as_select()) + .load::(&mut connection)?; - Ok(res) + Ok(res) } pub async fn load_attribute<'a>( - id: i32, - external_id: &str, - ctx: &Context<'a>, + id: i32, + external_id: &str, + ctx: &Context<'a>, ) -> async_graphql::Result> { - use crate::persistence::schema::activity_attribute; - - let store = ctx.data_unchecked::(); - - let mut connection = store.pool.get()?; - - Ok(activity_attribute::table - .filter( - activity_attribute::activity_id - .eq(id) - .and(activity_attribute::typename.eq(external_id)), - ) - .select(activity_attribute::value) - .first::(&mut connection) - .optional()? - .as_deref() - .map(serde_json::from_str) - .transpose()?) + use crate::persistence::schema::activity_attribute; + + let store = ctx.data_unchecked::(); + + let mut connection = store.pool.get()?; + + Ok(activity_attribute::table + .filter( + activity_attribute::activity_id + .eq(id) + .and(activity_attribute::typename.eq(external_id)), + ) + .select(activity_attribute::value) + .first::(&mut connection) + .optional()? + .as_deref() + .map(serde_json::from_str) + .transpose()?) } diff --git a/crates/api/src/chronicle_graphql/agent.rs b/crates/api/src/chronicle_graphql/agent.rs index d26df051b..a7507e34f 100644 --- a/crates/api/src/chronicle_graphql/agent.rs +++ b/crates/api/src/chronicle_graphql/agent.rs @@ -6,108 +6,105 @@ use common::prov::Role; use diesel::prelude::*; pub async fn namespace<'a>( - namespace_id: i32, - ctx: &Context<'a>, + namespace_id: i32, + ctx: &Context<'a>, ) -> async_graphql::Result { - use crate::persistence::schema::namespace::{self, dsl}; - let store = ctx.data_unchecked::(); + use crate::persistence::schema::namespace::{self, dsl}; + let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; + let mut connection = store.pool.get()?; - Ok(namespace::table - .filter(dsl::id.eq(namespace_id)) - .first::(&mut connection)?) + Ok(namespace::table + .filter(dsl::id.eq(namespace_id)) + .first::(&mut connection)?) } pub async fn identity<'a>( - identity_id: Option, - ctx: &Context<'a>, + identity_id: Option, + ctx: &Context<'a>, ) -> async_graphql::Result> { - use crate::persistence::schema::identity::{self, dsl}; - let store = ctx.data_unchecked::(); - - let mut connection = store.pool.get()?; - - if let Some(identity_id) = identity_id { - Ok(identity::table - .filter(dsl::id.eq(identity_id)) - .first::(&mut connection) - .optional()?) - } else { - Ok(None) - } + use crate::persistence::schema::identity::{self, dsl}; + let store = ctx.data_unchecked::(); + + let mut connection = store.pool.get()?; + + if let Some(identity_id) = identity_id { + Ok(identity::table + .filter(dsl::id.eq(identity_id)) + .first::(&mut connection) + .optional()?) + } else { + Ok(None) + } } pub async fn acted_on_behalf_of<'a>( - id: i32, - ctx: &Context<'a>, + id: i32, + ctx: &Context<'a>, ) -> async_graphql::Result)>> { - use crate::persistence::schema::{ - agent as agentdsl, - delegation::{self, dsl}, - }; - - let store = ctx.data_unchecked::(); - - let mut connection = store.pool.get()?; - - Ok(delegation::table - .filter(dsl::delegate_id.eq(id)) - .inner_join(agentdsl::table.on(dsl::responsible_id.eq(agentdsl::id))) - .order(agentdsl::external_id) - .select((Agent::as_select(), dsl::role)) - .load::<(Agent, Role)>(&mut connection)? - .into_iter() - .map(|(a, r)| (a, if r.0.is_empty() { None } else { Some(r) })) - .collect()) + use crate::persistence::schema::{ + agent as agentdsl, + delegation::{self, dsl}, + }; + + let store = ctx.data_unchecked::(); + + let mut connection = store.pool.get()?; + + Ok(delegation::table + .filter(dsl::delegate_id.eq(id)) + .inner_join(agentdsl::table.on(dsl::responsible_id.eq(agentdsl::id))) + .order(agentdsl::external_id) + .select((Agent::as_select(), dsl::role)) + .load::<(Agent, Role)>(&mut connection)? + .into_iter() + .map(|(a, r)| (a, if r.0.is_empty() { None } else { Some(r) })) + .collect()) } -/// Return the entities an agent has attributed to it along with the roles in which they were attributed +/// Return the entities an agent has attributed to it along with the roles in which they were +/// attributed pub async fn attribution<'a>( - id: i32, - ctx: &Context<'a>, + id: i32, + ctx: &Context<'a>, ) -> async_graphql::Result)>> { - use crate::persistence::schema::{ - attribution::{self, dsl}, - entity as entity_dsl, - }; - - let store = ctx.data_unchecked::(); - - let mut connection = store.pool.get()?; - - Ok(attribution::table - .filter(dsl::agent_id.eq(id)) - .inner_join(entity_dsl::table.on(dsl::entity_id.eq(entity_dsl::id))) - .order(entity_dsl::external_id) - .select((Entity::as_select(), dsl::role)) - .load::<(Entity, Role)>(&mut connection)? - .into_iter() - .map(|(entity, role)| (entity, if role.0.is_empty() { None } else { Some(role) })) - .collect()) + use crate::persistence::schema::{ + attribution::{self, dsl}, + entity as entity_dsl, + }; + + let store = ctx.data_unchecked::(); + + let mut connection = store.pool.get()?; + + Ok(attribution::table + .filter(dsl::agent_id.eq(id)) + .inner_join(entity_dsl::table.on(dsl::entity_id.eq(entity_dsl::id))) + .order(entity_dsl::external_id) + .select((Entity::as_select(), dsl::role)) + .load::<(Entity, Role)>(&mut connection)? + .into_iter() + .map(|(entity, role)| (entity, if role.0.is_empty() { None } else { Some(role) })) + .collect()) } pub async fn load_attribute<'a>( - id: i32, - external_id: &str, - ctx: &Context<'a>, + id: i32, + external_id: &str, + ctx: &Context<'a>, ) -> async_graphql::Result> { - use crate::persistence::schema::agent_attribute; - - let store = ctx.data_unchecked::(); - - let mut connection = store.pool.get()?; - - Ok(agent_attribute::table - .filter( - agent_attribute::agent_id - .eq(id) - .and(agent_attribute::typename.eq(external_id)), - ) - .select(agent_attribute::value) - .first::(&mut connection) - .optional()? - .as_deref() - .map(serde_json::from_str) - .transpose()?) + use crate::persistence::schema::agent_attribute; + + let store = ctx.data_unchecked::(); + + let mut connection = store.pool.get()?; + + Ok(agent_attribute::table + .filter(agent_attribute::agent_id.eq(id).and(agent_attribute::typename.eq(external_id))) + .select(agent_attribute::value) + .first::(&mut connection) + .optional()? + .as_deref() + .map(serde_json::from_str) + .transpose()?) } diff --git a/crates/api/src/chronicle_graphql/authorization.rs b/crates/api/src/chronicle_graphql/authorization.rs index c86c579b8..58e4ba997 100644 --- a/crates/api/src/chronicle_graphql/authorization.rs +++ b/crates/api/src/chronicle_graphql/authorization.rs @@ -12,196 +12,186 @@ use super::{JwksUri, UserInfoUri}; #[derive(Debug, Error)] pub enum Error { - #[error("Base64 decoding failure: {0}", source)] - Base64 { - #[from] - source: base64::DecodeError, - }, - #[error("JSON decoding failure: {0}", source)] - Json { - #[from] - source: serde_json::Error, - }, - #[error("JWT validation failure: {0}", source)] - Jwks { - #[from] - source: jwtk::Error, - }, - #[error("web access failure: {0}", source)] - Reqwest { - #[from] - source: reqwest::Error, - }, - #[error("formatting error: {0}", message)] - Format { message: String }, - #[error("unexpected response: {0} responded with status {1}", server, status)] - UnexpectedResponse { server: String, status: StatusCode }, + #[error("Base64 decoding failure: {0}", source)] + Base64 { + #[from] + source: base64::DecodeError, + }, + #[error("JSON decoding failure: {0}", source)] + Json { + #[from] + source: serde_json::Error, + }, + #[error("JWT validation failure: {0}", source)] + Jwks { + #[from] + source: jwtk::Error, + }, + #[error("web access failure: {0}", source)] + Reqwest { + #[from] + source: reqwest::Error, + }, + #[error("formatting error: {0}", message)] + Format { message: String }, + #[error("unexpected response: {0} responded with status {1}", server, status)] + UnexpectedResponse { server: String, status: StatusCode }, } pub struct TokenChecker { - client: reqwest::Client, - verifier: Option, - jwks_uri: Option, - userinfo_uri: Option, - userinfo_cache: Arc>>>, + client: reqwest::Client, + verifier: Option, + jwks_uri: Option, + userinfo_uri: Option, + userinfo_cache: Arc>>>, } impl TokenChecker { - #[instrument(level = "debug")] - pub fn new( - jwks_uri: Option<&JwksUri>, - userinfo_uri: Option<&UserInfoUri>, - cache_expiry_seconds: u32, - ) -> Self { - Self { - client: reqwest::Client::new(), - verifier: jwks_uri.map(|uri| { - RemoteJwksVerifier::new( - uri.full_uri(), - None, - Duration::from_secs(cache_expiry_seconds.into()), - ) - }), - jwks_uri: jwks_uri.cloned(), - userinfo_uri: userinfo_uri.cloned(), - userinfo_cache: Arc::new(Mutex::new(TimedCache::with_lifespan( - cache_expiry_seconds.into(), - ))), - } - } + #[instrument(level = "debug")] + pub fn new( + jwks_uri: Option<&JwksUri>, + userinfo_uri: Option<&UserInfoUri>, + cache_expiry_seconds: u32, + ) -> Self { + Self { + client: reqwest::Client::new(), + verifier: jwks_uri.map(|uri| { + RemoteJwksVerifier::new( + uri.full_uri(), + None, + Duration::from_secs(cache_expiry_seconds.into()), + ) + }), + jwks_uri: jwks_uri.cloned(), + userinfo_uri: userinfo_uri.cloned(), + userinfo_cache: Arc::new(Mutex::new(TimedCache::with_lifespan( + cache_expiry_seconds.into(), + ))), + } + } - pub async fn check_status(&self) -> Result<(), Error> { - if let Some(uri) = &self.jwks_uri { - let status = self.client.get(uri.full_uri()).send().await?.status(); - // should respond with JSON web key set - if !status.is_success() { - tracing::warn!("{uri:?} returns {status}"); - return Err(Error::UnexpectedResponse { - server: format!("{uri:?}"), - status, - }); - } - } - if let Some(uri) = &self.userinfo_uri { - let status = self.client.get(uri.full_uri()).send().await?.status(); - // should require an authorization token - if !status.is_client_error() || status == StatusCode::NOT_FOUND { - tracing::warn!("{uri:?} without authorization token returns {status}"); - return Err(Error::UnexpectedResponse { - server: format!("{uri:?}"), - status, - }); - } - } - Ok(()) - } + pub async fn check_status(&self) -> Result<(), Error> { + if let Some(uri) = &self.jwks_uri { + let status = self.client.get(uri.full_uri()).send().await?.status(); + // should respond with JSON web key set + if !status.is_success() { + tracing::warn!("{uri:?} returns {status}"); + return Err(Error::UnexpectedResponse { server: format!("{uri:?}"), status }) + } + } + if let Some(uri) = &self.userinfo_uri { + let status = self.client.get(uri.full_uri()).send().await?.status(); + // should require an authorization token + if !status.is_client_error() || status == StatusCode::NOT_FOUND { + tracing::warn!("{uri:?} without authorization token returns {status}"); + return Err(Error::UnexpectedResponse { server: format!("{uri:?}"), status }) + } + } + Ok(()) + } - #[instrument(level = "trace", skip_all, err)] - async fn attempt_jwt(&self, token: &str) -> Result, Error> { - use base64::engine::general_purpose::{GeneralPurpose, URL_SAFE_NO_PAD}; - const BASE64_ENGINE: GeneralPurpose = URL_SAFE_NO_PAD; + #[instrument(level = "trace", skip_all, err)] + async fn attempt_jwt(&self, token: &str) -> Result, Error> { + use base64::engine::general_purpose::{GeneralPurpose, URL_SAFE_NO_PAD}; + const BASE64_ENGINE: GeneralPurpose = URL_SAFE_NO_PAD; - if let Some(verifier) = &self.verifier { - verifier.verify::>(token).await?; - } else { - return Err(Error::Format { - message: "no JWKS endpoint configured".to_string(), - }); - } + if let Some(verifier) = &self.verifier { + verifier.verify::>(token).await?; + } else { + return Err(Error::Format { message: "no JWKS endpoint configured".to_string() }) + } - // JWT is composed of three base64-encoded components - let components = token - .split('.') - .map(|component| BASE64_ENGINE.decode(component)) - .collect::>, base64::DecodeError>>()?; - if components.len() != 3 { - return Err(Error::Format { - message: format!("JWT has unexpected format: {token}"), - }); - }; + // JWT is composed of three base64-encoded components + let components = token + .split('.') + .map(|component| BASE64_ENGINE.decode(component)) + .collect::>, base64::DecodeError>>()?; + if components.len() != 3 { + return Err(Error::Format { message: format!("JWT has unexpected format: {token}") }) + }; - if let Value::Object(claims) = serde_json::from_slice(components[1].as_slice())? { - Ok(claims) - } else { - Err(Error::Format { - message: format!("JWT claims have unexpected format: {:?}", components[1]), - }) - } - } + if let Value::Object(claims) = serde_json::from_slice(components[1].as_slice())? { + Ok(claims) + } else { + Err(Error::Format { + message: format!("JWT claims have unexpected format: {:?}", components[1]), + }) + } + } - #[instrument(level = "debug", skip_all, err)] - pub async fn verify_token(&self, token: &str) -> Result, Error> { - let mut claims = Map::new(); - let mut error = None; - match self.attempt_jwt(token).await { - Ok(claims_as_provided) => claims.extend(claims_as_provided), - Err(Error::Jwks { source }) => { - match source { - jwtk::Error::IoError(_) | jwtk::Error::Reqwest(_) => { - tracing::error!(fatal_error = ?source); - super::trigger_shutdown(); - } - _ => (), - } - return Err(Error::Jwks { source }); // abort on JWKS verifier failure - } - Err(err) => error = Some(err), // could tolerate error from what may be opaque token - }; - if let Some(userinfo_uri) = &self.userinfo_uri { - let mut cache = self.userinfo_cache.lock().await; - if let Some(claims_from_userinfo) = cache.cache_get(&token.to_string()) { - tracing::trace!("userinfo cache hit"); - error = None; - claims.extend(claims_from_userinfo.clone()); - } else { - tracing::trace!("userinfo cache miss"); - drop(cache); - let request = self - .client - .get(userinfo_uri.full_uri()) - .header("Authorization", format!("Bearer {token}")); - let response = request.send().await?; - cache = self.userinfo_cache.lock().await; - if response.status() == 200 { - let response_text = &response.text().await?; - if let Ok(claims_from_userinfo) = self.attempt_jwt(response_text).await { - error = None; - claims.extend(claims_from_userinfo.clone()); - cache.cache_set(token.to_string(), claims_from_userinfo); - } else if let Ok(Value::Object(claims_from_userinfo)) = - serde_json::from_str(response_text) - { - error = None; - claims.extend(claims_from_userinfo.clone()); - cache.cache_set(token.to_string(), claims_from_userinfo); - } else { - error = Some(Error::Format { - message: format!( - "UserInfo response has unexpected format: {response_text}" - ), - }); - tracing::error!(fatal_error = ?error.as_ref().unwrap()); - super::trigger_shutdown(); - } - } else { - if error.is_some() { - tracing::trace!("first error before UserInfo was {error:?}"); - } - error = Some(Error::UnexpectedResponse { - server: format!("{userinfo_uri:?}"), - status: response.status(), - }); - if response.status() != StatusCode::UNAUTHORIZED { - tracing::error!(fatal_error = ?error.as_ref().unwrap()); - super::trigger_shutdown(); - } - } - } - } - if let Some(error) = error { - Err(error) - } else { - Ok(claims) - } - } + #[instrument(level = "debug", skip_all, err)] + pub async fn verify_token(&self, token: &str) -> Result, Error> { + let mut claims = Map::new(); + let mut error = None; + match self.attempt_jwt(token).await { + Ok(claims_as_provided) => claims.extend(claims_as_provided), + Err(Error::Jwks { source }) => { + match source { + jwtk::Error::IoError(_) | jwtk::Error::Reqwest(_) => { + tracing::error!(fatal_error = ?source); + super::trigger_shutdown(); + }, + _ => (), + } + return Err(Error::Jwks { source }) // abort on JWKS verifier failure + }, + Err(err) => error = Some(err), // could tolerate error from what may be opaque token + }; + if let Some(userinfo_uri) = &self.userinfo_uri { + let mut cache = self.userinfo_cache.lock().await; + if let Some(claims_from_userinfo) = cache.cache_get(&token.to_string()) { + tracing::trace!("userinfo cache hit"); + error = None; + claims.extend(claims_from_userinfo.clone()); + } else { + tracing::trace!("userinfo cache miss"); + drop(cache); + let request = self + .client + .get(userinfo_uri.full_uri()) + .header("Authorization", format!("Bearer {token}")); + let response = request.send().await?; + cache = self.userinfo_cache.lock().await; + if response.status() == 200 { + let response_text = &response.text().await?; + if let Ok(claims_from_userinfo) = self.attempt_jwt(response_text).await { + error = None; + claims.extend(claims_from_userinfo.clone()); + cache.cache_set(token.to_string(), claims_from_userinfo); + } else if let Ok(Value::Object(claims_from_userinfo)) = + serde_json::from_str(response_text) + { + error = None; + claims.extend(claims_from_userinfo.clone()); + cache.cache_set(token.to_string(), claims_from_userinfo); + } else { + error = Some(Error::Format { + message: format!( + "UserInfo response has unexpected format: {response_text}" + ), + }); + tracing::error!(fatal_error = ?error.as_ref().unwrap()); + super::trigger_shutdown(); + } + } else { + if error.is_some() { + tracing::trace!("first error before UserInfo was {error:?}"); + } + error = Some(Error::UnexpectedResponse { + server: format!("{userinfo_uri:?}"), + status: response.status(), + }); + if response.status() != StatusCode::UNAUTHORIZED { + tracing::error!(fatal_error = ?error.as_ref().unwrap()); + super::trigger_shutdown(); + } + } + } + } + if let Some(error) = error { + Err(error) + } else { + Ok(claims) + } + } } diff --git a/crates/api/src/chronicle_graphql/cursor_query.rs b/crates/api/src/chronicle_graphql/cursor_query.rs index 0f68b64eb..521c2fa8d 100644 --- a/crates/api/src/chronicle_graphql/cursor_query.rs +++ b/crates/api/src/chronicle_graphql/cursor_query.rs @@ -1,6 +1,6 @@ use async_graphql::{ - connection::{Edge, EmptyFields}, - OutputType, + connection::{Edge, EmptyFields}, + OutputType, }; use diesel::{pg::Pg, prelude::*, query_builder::*, r2d2::ConnectionManager, sql_types::BigInt}; use r2d2::PooledConnection; @@ -11,91 +11,85 @@ const DEFAULT_PAGE_SIZE: i32 = 10; #[derive(QueryId)] pub struct CursorPosition { - query: T, - pub(crate) start: i64, - pub(crate) limit: i64, + query: T, + pub(crate) start: i64, + pub(crate) limit: i64, } pub trait Cursorize: Sized { - fn cursor( - self, - after: Option, - before: Option, - first: Option, - last: Option, - ) -> CursorPosition; + fn cursor( + self, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> CursorPosition; } pub fn project_to_nodes( - rx: I, - start: i64, - limit: i64, + rx: I, + start: i64, + limit: i64, ) -> async_graphql::connection::Connection where - T: OutputType, - I: IntoIterator, + T: OutputType, + I: IntoIterator, { - let rx = Vec::from_iter(rx); - let mut gql = async_graphql::connection::Connection::new( - rx.first().map(|(_, _total)| start > 0).unwrap_or(false), - rx.first() - .map(|(_, total)| start + limit < *total) - .unwrap_or(false), - ); + let rx = Vec::from_iter(rx); + let mut gql = async_graphql::connection::Connection::new( + rx.first().map(|(_, _total)| start > 0).unwrap_or(false), + rx.first().map(|(_, total)| start + limit < *total).unwrap_or(false), + ); - gql.edges.append( - &mut rx - .into_iter() - .enumerate() - .map(|(pos, (agent, _count))| { - Edge::with_additional_fields((pos as i32) + (start as i32), agent, EmptyFields) - }) - .collect(), - ); - gql + gql.edges.append( + &mut rx + .into_iter() + .enumerate() + .map(|(pos, (agent, _count))| { + Edge::with_additional_fields((pos as i32) + (start as i32), agent, EmptyFields) + }) + .collect(), + ); + gql } impl Cursorize for T { - fn cursor( - self, - after: Option, - before: Option, - first: Option, - last: Option, - ) -> CursorPosition { - let mut start = after.map(|after| after + 1).unwrap_or(0) as usize; - let mut end = before.unwrap_or(DEFAULT_PAGE_SIZE) as usize; - if let Some(first) = first { - end = start + first - } - if let Some(last) = last { - start = if last > end - start { end } else { end - last }; - }; + fn cursor( + self, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> CursorPosition { + let mut start = after.map(|after| after + 1).unwrap_or(0) as usize; + let mut end = before.unwrap_or(DEFAULT_PAGE_SIZE) as usize; + if let Some(first) = first { + end = start + first + } + if let Some(last) = last { + start = if last > end - start { end } else { end - last }; + }; - CursorPosition { - query: self, - start: start as _, - limit: (end - start) as _, - } - } + CursorPosition { query: self, start: start as _, limit: (end - start) as _ } + } } impl QueryFragment for CursorPosition where - T: QueryFragment, + T: QueryFragment, { - fn walk_ast<'a>(&'a self, mut out: AstPass<'_, 'a, Pg>) -> QueryResult<()> { - out.push_sql("SELECT *, COUNT(*) OVER () FROM ("); - self.query.walk_ast(out.reborrow())?; - out.push_sql(") t LIMIT "); - out.push_bind_param::(&(self.limit))?; - out.push_sql(" OFFSET "); - out.push_bind_param::(&self.start)?; - Ok(()) - } + fn walk_ast<'a>(&'a self, mut out: AstPass<'_, 'a, Pg>) -> QueryResult<()> { + out.push_sql("SELECT *, COUNT(*) OVER () FROM ("); + self.query.walk_ast(out.reborrow())?; + out.push_sql(") t LIMIT "); + out.push_bind_param::(&(self.limit))?; + out.push_sql(" OFFSET "); + out.push_bind_param::(&self.start)?; + Ok(()) + } } impl Query for CursorPosition { - type SqlType = (T::SqlType, BigInt); + type SqlType = (T::SqlType, BigInt); } impl RunQueryDsl for CursorPosition {} diff --git a/crates/api/src/chronicle_graphql/entity.rs b/crates/api/src/chronicle_graphql/entity.rs index 74dc5a467..317275c0d 100644 --- a/crates/api/src/chronicle_graphql/entity.rs +++ b/crates/api/src/chronicle_graphql/entity.rs @@ -4,145 +4,146 @@ use common::prov::{operations::DerivationType, Role}; use diesel::prelude::*; async fn typed_derivation<'a>( - id: i32, - ctx: &Context<'a>, - typ: DerivationType, + id: i32, + ctx: &Context<'a>, + typ: DerivationType, ) -> async_graphql::Result> { - use crate::persistence::schema::{ - derivation::{self, dsl}, - entity as entitydsl, - }; + use crate::persistence::schema::{ + derivation::{self, dsl}, + entity as entitydsl, + }; - let store = ctx.data_unchecked::(); + let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; + let mut connection = store.pool.get()?; - let res = derivation::table - .filter(dsl::generated_entity_id.eq(id).and(dsl::typ.eq(typ))) - .inner_join(entitydsl::table.on(dsl::used_entity_id.eq(entitydsl::id))) - .select(Entity::as_select()) - .load::(&mut connection)?; + let res = derivation::table + .filter(dsl::generated_entity_id.eq(id).and(dsl::typ.eq(typ))) + .inner_join(entitydsl::table.on(dsl::used_entity_id.eq(entitydsl::id))) + .select(Entity::as_select()) + .load::(&mut connection)?; - Ok(res) + Ok(res) } pub async fn namespace<'a>( - namespace_id: i32, - ctx: &Context<'a>, + namespace_id: i32, + ctx: &Context<'a>, ) -> async_graphql::Result { - use crate::persistence::schema::namespace::{self, dsl}; + use crate::persistence::schema::namespace::{self, dsl}; - let store = ctx.data_unchecked::(); + let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; + let mut connection = store.pool.get()?; - Ok(namespace::table - .filter(dsl::id.eq(namespace_id)) - .first::(&mut connection)?) + Ok(namespace::table + .filter(dsl::id.eq(namespace_id)) + .first::(&mut connection)?) } -/// Return the agents to which an entity was attributed along with the roles in which it was attributed +/// Return the agents to which an entity was attributed along with the roles in which it was +/// attributed pub async fn was_attributed_to<'a>( - id: i32, - ctx: &Context<'a>, + id: i32, + ctx: &Context<'a>, ) -> async_graphql::Result)>> { - use crate::persistence::schema::{agent, attribution}; - - let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; - - let res = attribution::table - .filter(attribution::dsl::entity_id.eq(id)) - .inner_join(agent::table) - .order(agent::external_id) - .select((Agent::as_select(), attribution::role)) - .load::<(Agent, Role)>(&mut connection)? - .into_iter() - .map(|(agent, role)| { - let role = if role.0.is_empty() { None } else { Some(role) }; - (agent, role) - }) - .collect(); - - Ok(res) + use crate::persistence::schema::{agent, attribution}; + + let store = ctx.data_unchecked::(); + let mut connection = store.pool.get()?; + + let res = attribution::table + .filter(attribution::dsl::entity_id.eq(id)) + .inner_join(agent::table) + .order(agent::external_id) + .select((Agent::as_select(), attribution::role)) + .load::<(Agent, Role)>(&mut connection)? + .into_iter() + .map(|(agent, role)| { + let role = if role.0.is_empty() { None } else { Some(role) }; + (agent, role) + }) + .collect(); + + Ok(res) } pub async fn was_generated_by<'a>( - id: i32, - ctx: &Context<'a>, + id: i32, + ctx: &Context<'a>, ) -> async_graphql::Result> { - use crate::persistence::schema::generation::{self, dsl}; + use crate::persistence::schema::generation::{self, dsl}; - let store = ctx.data_unchecked::(); + let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; + let mut connection = store.pool.get()?; - let res = generation::table - .filter(dsl::generated_entity_id.eq(id)) - .inner_join(crate::persistence::schema::activity::table) - .select(Activity::as_select()) - .load::(&mut connection)?; + let res = generation::table + .filter(dsl::generated_entity_id.eq(id)) + .inner_join(crate::persistence::schema::activity::table) + .select(Activity::as_select()) + .load::(&mut connection)?; - Ok(res) + Ok(res) } pub async fn was_derived_from<'a>( - id: i32, - ctx: &Context<'a>, + id: i32, + ctx: &Context<'a>, ) -> async_graphql::Result> { - use crate::persistence::schema::{ - derivation::{self, dsl}, - entity as entitydsl, - }; + use crate::persistence::schema::{ + derivation::{self, dsl}, + entity as entitydsl, + }; - let store = ctx.data_unchecked::(); + let store = ctx.data_unchecked::(); - let mut connection = store.pool.get()?; + let mut connection = store.pool.get()?; - let res = derivation::table - .filter(dsl::generated_entity_id.eq(id)) - .inner_join(entitydsl::table.on(dsl::used_entity_id.eq(entitydsl::id))) - .select(Entity::as_select()) - .load::(&mut connection)?; + let res = derivation::table + .filter(dsl::generated_entity_id.eq(id)) + .inner_join(entitydsl::table.on(dsl::used_entity_id.eq(entitydsl::id))) + .select(Entity::as_select()) + .load::(&mut connection)?; - Ok(res) + Ok(res) } pub async fn had_primary_source<'a>( - id: i32, - ctx: &Context<'a>, + id: i32, + ctx: &Context<'a>, ) -> async_graphql::Result> { - typed_derivation(id, ctx, DerivationType::PrimarySource).await + typed_derivation(id, ctx, DerivationType::PrimarySource).await } pub async fn was_revision_of<'a>(id: i32, ctx: &Context<'a>) -> async_graphql::Result> { - typed_derivation(id, ctx, DerivationType::Revision).await + typed_derivation(id, ctx, DerivationType::Revision).await } pub async fn was_quoted_from<'a>(id: i32, ctx: &Context<'a>) -> async_graphql::Result> { - typed_derivation(id, ctx, DerivationType::Quotation).await + typed_derivation(id, ctx, DerivationType::Quotation).await } pub async fn load_attribute<'a>( - id: i32, - external_id: &str, - ctx: &Context<'a>, + id: i32, + external_id: &str, + ctx: &Context<'a>, ) -> async_graphql::Result> { - use crate::persistence::schema::entity_attribute; - - let store = ctx.data_unchecked::(); - - let mut connection = store.pool.get()?; - - Ok(entity_attribute::table - .filter( - entity_attribute::entity_id - .eq(id) - .and(entity_attribute::typename.eq(external_id)), - ) - .select(entity_attribute::value) - .first::(&mut connection) - .optional()? - .as_deref() - .map(serde_json::from_str) - .transpose()?) + use crate::persistence::schema::entity_attribute; + + let store = ctx.data_unchecked::(); + + let mut connection = store.pool.get()?; + + Ok(entity_attribute::table + .filter( + entity_attribute::entity_id + .eq(id) + .and(entity_attribute::typename.eq(external_id)), + ) + .select(entity_attribute::value) + .first::(&mut connection) + .optional()? + .as_deref() + .map(serde_json::from_str) + .transpose()?) } diff --git a/crates/api/src/chronicle_graphql/mod.rs b/crates/api/src/chronicle_graphql/mod.rs index a2aa55943..e2b80a510 100644 --- a/crates/api/src/chronicle_graphql/mod.rs +++ b/crates/api/src/chronicle_graphql/mod.rs @@ -1,51 +1,51 @@ use async_graphql::{ - extensions::OpenTelemetry, - http::{playground_source, GraphQLPlaygroundConfig, ALL_WEBSOCKET_PROTOCOLS}, - scalar, Context, Enum, Error, ErrorExtensions, Object, ObjectType, Schema, ServerError, - SimpleObject, Subscription, SubscriptionType, + extensions::OpenTelemetry, + http::{playground_source, GraphQLPlaygroundConfig, ALL_WEBSOCKET_PROTOCOLS}, + scalar, Context, Enum, Error, ErrorExtensions, Object, ObjectType, Schema, ServerError, + SimpleObject, Subscription, SubscriptionType, }; use async_graphql_poem::{ - GraphQL, GraphQLBatchRequest, GraphQLBatchResponse, GraphQLProtocol, GraphQLSubscription, - GraphQLWebSocket, + GraphQL, GraphQLBatchRequest, GraphQLBatchResponse, GraphQLProtocol, GraphQLSubscription, + GraphQLWebSocket, }; use chrono::NaiveDateTime; use common::{ - identity::{AuthId, IdentityError, JwtClaims, OpaData, SignedIdentity}, - ledger::{SubmissionError, SubmissionStage}, - opa::{ExecutorContext, OpaExecutorError}, - prov::{ - to_json_ld::ToJson, ChronicleIri, ChronicleTransactionId, ExternalId, ExternalIdPart, - ProvModel, - }, + identity::{AuthId, IdentityError, JwtClaims, OpaData, SignedIdentity}, + ledger::{SubmissionError, SubmissionStage}, + opa::{ExecutorContext, OpaExecutorError}, + prov::{ + to_json_ld::ToJson, ChronicleIri, ChronicleTransactionId, ExternalId, ExternalIdPart, + ProvModel, + }, }; use derivative::*; use diesel::{ - prelude::*, - r2d2::{ConnectionManager, Pool}, - PgConnection, Queryable, + prelude::*, + r2d2::{ConnectionManager, Pool}, + PgConnection, Queryable, }; use futures::Stream; use lazy_static::lazy_static; use poem::{ - get, handler, - http::{HeaderValue, StatusCode}, - listener::{Listener, TcpListener}, - post, - web::{ - headers::authorization::{Bearer, Credentials}, - Html, - }, - Endpoint, IntoResponse, Route, Server, + get, handler, + http::{HeaderValue, StatusCode}, + listener::{Listener, TcpListener}, + post, + web::{ + headers::authorization::{Bearer, Credentials}, + Html, + }, + Endpoint, IntoResponse, Route, Server, }; use r2d2::PooledConnection; use serde::{Deserialize, Serialize}; use serde_json::json; use std::{ - collections::{BTreeSet, HashMap}, - fmt::Display, - net::SocketAddr, - str::FromStr, - sync::Arc, + collections::{BTreeSet, HashMap}, + fmt::Display, + net::SocketAddr, + str::FromStr, + sync::Arc, }; use thiserror::Error; use tokio::sync::{broadcast::error::RecvError, Semaphore}; @@ -69,20 +69,20 @@ pub type AuthorizationError = authorization::Error; #[derive(Default, Queryable, Selectable, SimpleObject)] #[diesel(table_name = crate::persistence::schema::agent)] pub struct Agent { - pub id: i32, - pub external_id: String, - pub namespace_id: i32, - pub domaintype: Option, - pub current: i32, - pub identity_id: Option, + pub id: i32, + pub external_id: String, + pub namespace_id: i32, + pub domaintype: Option, + pub current: i32, + pub identity_id: Option, } #[derive(Default, Queryable, Selectable)] #[diesel(table_name = crate::persistence::schema::identity)] pub struct Identity { - pub id: i32, - pub namespace_id: i32, - pub public_key: String, + pub id: i32, + pub namespace_id: i32, + pub public_key: String, } #[Object] @@ -92,36 +92,36 @@ pub struct Identity { /// signing identity via `chronicle:hasIdentity` and historical identities via /// `chronicle:hadIdentity`. impl Identity { - async fn public_key(&self) -> &str { - &self.public_key - } + async fn public_key(&self) -> &str { + &self.public_key + } } #[derive(Default, Queryable, Selectable, SimpleObject)] #[diesel(table_name = crate::persistence::schema::activity)] pub struct Activity { - pub id: i32, - pub external_id: String, - pub namespace_id: i32, - pub domaintype: Option, - pub started: Option, - pub ended: Option, + pub id: i32, + pub external_id: String, + pub namespace_id: i32, + pub domaintype: Option, + pub started: Option, + pub ended: Option, } #[derive(Queryable, Selectable, SimpleObject)] #[diesel(table_name = crate::persistence::schema::entity)] pub struct Entity { - pub id: i32, - pub external_id: String, - pub namespace_id: i32, - pub domaintype: Option, + pub id: i32, + pub external_id: String, + pub namespace_id: i32, + pub domaintype: Option, } #[derive(Default, Queryable)] pub struct Namespace { - _id: i32, - uuid: String, - external_id: String, + _id: i32, + uuid: String, + external_id: String, } #[Object] @@ -131,13 +131,13 @@ pub struct Namespace { /// In order to work on the same namespace discrete Chronicle instances must share /// the uuid part. impl Namespace { - async fn external_id(&self) -> &str { - &self.external_id - } + async fn external_id(&self) -> &str { + &self.external_id + } - async fn uuid(&self) -> &str { - &self.uuid - } + async fn uuid(&self) -> &str { + &self.uuid + } } #[derive(Queryable, SimpleObject)] @@ -152,9 +152,9 @@ impl Namespace { /// * `tx_id` - transaction id for a submitted operation; returns `null` if `submission_result` /// is `SubmissionResult::AlreadyRecorded` pub struct Submission { - context: String, - submission_result: SubmissionResult, - tx_id: Option, + context: String, + submission_result: SubmissionResult, + tx_id: Option, } #[derive(Enum, PartialEq, Eq, Clone, Copy)] @@ -165,26 +165,26 @@ pub struct Submission { /// * `Submission` - operation has been submitted /// * `AlreadyRecorded` - operation will not result in data changes and has not been submitted pub enum SubmissionResult { - Submission, - AlreadyRecorded, + Submission, + AlreadyRecorded, } impl Submission { - pub fn from_submission(subject: &ChronicleIri, tx_id: &ChronicleTransactionId) -> Self { - Submission { - context: subject.to_string(), - submission_result: SubmissionResult::Submission, - tx_id: Some(tx_id.to_string()), - } - } - - pub fn from_already_recorded(subject: &ChronicleIri) -> Self { - Submission { - context: subject.to_string(), - submission_result: SubmissionResult::AlreadyRecorded, - tx_id: None, - } - } + pub fn from_submission(subject: &ChronicleIri, tx_id: &ChronicleTransactionId) -> Self { + Submission { + context: subject.to_string(), + submission_result: SubmissionResult::Submission, + tx_id: Some(tx_id.to_string()), + } + } + + pub fn from_already_recorded(subject: &ChronicleIri) -> Self { + Submission { + context: subject.to_string(), + submission_result: SubmissionResult::AlreadyRecorded, + tx_id: None, + } + } } /// # `TimelineOrder` @@ -192,88 +192,88 @@ impl Submission { /// Specify the order in which multiple results of query data are returned #[derive(Enum, Copy, Clone, Eq, PartialEq, Debug)] pub enum TimelineOrder { - NewestFirst, - OldestFirst, + NewestFirst, + OldestFirst, } #[derive(Error, Debug)] pub enum GraphQlError { - #[error("Database operation failed: {0}")] - Db(#[from] diesel::result::Error), + #[error("Database operation failed: {0}")] + Db(#[from] diesel::result::Error), - #[error("Connection pool error: {0}")] - R2d2(#[from] r2d2::Error), + #[error("Connection pool error: {0}")] + R2d2(#[from] r2d2::Error), - #[error("Database connection failed: {0}")] - DbConnection(#[from] diesel::ConnectionError), + #[error("Database connection failed: {0}")] + DbConnection(#[from] diesel::ConnectionError), - #[error("API: {0}")] - Api(#[from] crate::ApiError), + #[error("API: {0}")] + Api(#[from] crate::ApiError), - #[error("I/O: {0}")] - Io(#[from] std::io::Error), + #[error("I/O: {0}")] + Io(#[from] std::io::Error), } impl GraphQlError { - fn error_sources( - mut source: Option<&(dyn std::error::Error + 'static)>, - ) -> Option> { - /* Check if we have any sources to derive reasons from */ - if source.is_some() { - /* Add all the error sources to a list of reasons for the error */ - let mut reasons = Vec::new(); - while let Some(error) = source { - reasons.push(error.to_string()); - source = error.source(); - } - Some(reasons) - } else { - None - } - } + fn error_sources( + mut source: Option<&(dyn std::error::Error + 'static)>, + ) -> Option> { + /* Check if we have any sources to derive reasons from */ + if source.is_some() { + /* Add all the error sources to a list of reasons for the error */ + let mut reasons = Vec::new(); + while let Some(error) = source { + reasons.push(error.to_string()); + source = error.source(); + } + Some(reasons) + } else { + None + } + } } impl ErrorExtensions for GraphQlError { - // lets define our base extensions - fn extend(&self) -> Error { - Error::new(self.to_string()).extend_with(|_err, e| { - if let Some(reasons) = Self::error_sources(custom_error::Error::source(&self)) { - let mut i = 1; - for reason in reasons { - e.set(format!("reason {i}"), reason); - i += 1; - } - } - }) - } + // lets define our base extensions + fn extend(&self) -> Error { + Error::new(self.to_string()).extend_with(|_err, e| { + if let Some(reasons) = Self::error_sources(custom_error::Error::source(&self)) { + let mut i = 1; + for reason in reasons { + e.set(format!("reason {i}"), reason); + i += 1; + } + } + }) + } } #[derive(Derivative)] #[derivative(Debug)] pub struct Store { - #[derivative(Debug = "ignore")] - pub pool: Pool>, + #[derivative(Debug = "ignore")] + pub pool: Pool>, } impl Store { - pub fn new(pool: Pool>) -> Self { - Store { pool } - } + pub fn new(pool: Pool>) -> Self { + Store { pool } + } } pub struct Commit { - pub tx_id: String, + pub tx_id: String, } pub struct Rejection { - pub commit: Commit, - pub reason: String, + pub commit: Commit, + pub reason: String, } #[derive(Enum, PartialEq, Eq, Clone, Copy)] pub enum Stage { - Submit, - Commit, + Submit, + Commit, } #[derive(Serialize, Deserialize)] @@ -282,91 +282,88 @@ scalar!(Delta); #[derive(SimpleObject)] pub struct CommitIdentity { - identity: String, - signature: String, - verifying_key: String, + identity: String, + signature: String, + verifying_key: String, } impl From for CommitIdentity { - fn from(identity: SignedIdentity) -> Self { - CommitIdentity { - identity: identity.identity, - signature: identity - .signature - .map(|x| hex::encode(&*x)) - .unwrap_or_default(), - verifying_key: identity - .verifying_key - .map(|x| hex::encode(x.to_bytes())) - .unwrap_or_default(), - } - } + fn from(identity: SignedIdentity) -> Self { + CommitIdentity { + identity: identity.identity, + signature: identity.signature.map(|x| hex::encode(&*x)).unwrap_or_default(), + verifying_key: identity + .verifying_key + .map(|x| hex::encode(x.to_bytes())) + .unwrap_or_default(), + } + } } #[derive(SimpleObject)] pub struct CommitNotification { - pub stage: Stage, - pub tx_id: String, - pub error: Option, - pub delta: Option, - pub id: Option, + pub stage: Stage, + pub tx_id: String, + pub error: Option, + pub delta: Option, + pub id: Option, } impl CommitNotification { - pub fn from_submission(tx_id: &ChronicleTransactionId) -> Self { - CommitNotification { - stage: Stage::Submit, - tx_id: tx_id.to_string(), - error: None, - delta: None, - id: None, - } - } - - pub fn from_submission_failed(e: &SubmissionError) -> Self { - CommitNotification { - stage: Stage::Submit, - tx_id: e.tx_id().to_string(), - error: Some(e.to_string()), - delta: None, - id: None, - } - } - - pub fn from_contradiction( - tx_id: &ChronicleTransactionId, - contradiction: &str, - id: SignedIdentity, - ) -> Self { - CommitNotification { - stage: Stage::Commit, - tx_id: tx_id.to_string(), - error: Some(contradiction.to_string()), - delta: None, - id: Some(id.into()), - } - } - - pub async fn from_committed( - tx_id: &ChronicleTransactionId, - delta: Box, - id: SignedIdentity, - ) -> Result { - Ok(CommitNotification { - stage: Stage::Commit, - tx_id: tx_id.to_string(), - error: None, - delta: delta - .to_json() - .compact_stable_order() - .await - .ok() - .map(async_graphql::Value::from_json) - .transpose()? - .map(Delta), - id: Some(id.into()), - }) - } + pub fn from_submission(tx_id: &ChronicleTransactionId) -> Self { + CommitNotification { + stage: Stage::Submit, + tx_id: tx_id.to_string(), + error: None, + delta: None, + id: None, + } + } + + pub fn from_submission_failed(e: &SubmissionError) -> Self { + CommitNotification { + stage: Stage::Submit, + tx_id: e.tx_id().to_string(), + error: Some(e.to_string()), + delta: None, + id: None, + } + } + + pub fn from_contradiction( + tx_id: &ChronicleTransactionId, + contradiction: &str, + id: SignedIdentity, + ) -> Self { + CommitNotification { + stage: Stage::Commit, + tx_id: tx_id.to_string(), + error: Some(contradiction.to_string()), + delta: None, + id: Some(id.into()), + } + } + + pub async fn from_committed( + tx_id: &ChronicleTransactionId, + delta: Box, + id: SignedIdentity, + ) -> Result { + Ok(CommitNotification { + stage: Stage::Commit, + tx_id: tx_id.to_string(), + error: None, + delta: delta + .to_json() + .compact_stable_order() + .await + .ok() + .map(async_graphql::Value::from_json) + .transpose()? + .map(Delta), + id: Some(id.into()), + }) + } } pub struct Subscription; @@ -377,831 +374,786 @@ pub struct Subscription; /// /// [^note](https://graphql.org/blog/subscriptions-in-graphql-and-relay/) impl Subscription { - async fn commit_notifications<'a>( - &self, - ctx: &Context<'a>, - ) -> impl Stream { - let api = ctx.data_unchecked::().clone(); - let mut rx = api.notify_commit.subscribe(); - async_stream::stream! { - loop { - match rx.recv().await { - Ok(SubmissionStage::Submitted(Ok(submission))) => - yield CommitNotification::from_submission(&submission), - Ok(SubmissionStage::Committed(commit, id)) => { - let notify = CommitNotification::from_committed(&commit.tx_id, commit.delta, *id).await; - if let Ok(notify) = notify { - yield notify; - } else { - error!("Failed to convert commit to notification: {:?}", notify.err()); - } - } - Ok(SubmissionStage::NotCommitted((commit,contradiction, id))) => - yield CommitNotification::from_contradiction(&commit, &contradiction.to_string(), *id), - Ok(SubmissionStage::Submitted(Err(e))) => { - error!("Failed to submit: {:?}", e); - yield CommitNotification::from_submission_failed(&e); - } - Err(RecvError::Lagged(_)) => { - } - Err(_) => break - } - } - } - } + async fn commit_notifications<'a>( + &self, + ctx: &Context<'a>, + ) -> impl Stream { + let api = ctx.data_unchecked::().clone(); + let mut rx = api.notify_commit.subscribe(); + async_stream::stream! { + loop { + match rx.recv().await { + Ok(SubmissionStage::Submitted(Ok(submission))) => + yield CommitNotification::from_submission(&submission), + Ok(SubmissionStage::Committed(commit, id)) => { + let notify = CommitNotification::from_committed(&commit.tx_id, commit.delta, *id).await; + if let Ok(notify) = notify { + yield notify; + } else { + error!("Failed to convert commit to notification: {:?}", notify.err()); + } + } + Ok(SubmissionStage::NotCommitted((commit,contradiction, id))) => + yield CommitNotification::from_contradiction(&commit, &contradiction.to_string(), *id), + Ok(SubmissionStage::Submitted(Err(e))) => { + error!("Failed to submit: {:?}", e); + yield CommitNotification::from_submission_failed(&e); + } + Err(RecvError::Lagged(_)) => { + } + Err(_) => break + } + } + } + } } #[handler] async fn gql_playground() -> impl IntoResponse { - Html(playground_source( - GraphQLPlaygroundConfig::new("/").subscription_endpoint("/ws"), - )) + Html(playground_source(GraphQLPlaygroundConfig::new("/").subscription_endpoint("/ws"))) } #[derive(Debug, Clone)] pub struct ChronicleGraphQl where - Query: ObjectType + 'static, - Mutation: ObjectType + 'static, + Query: ObjectType + 'static, + Mutation: ObjectType + 'static, { - query: Query, - mutation: Mutation, + query: Query, + mutation: Mutation, } #[derive(Clone)] pub struct JwksUri { - uri: Url, + uri: Url, } impl JwksUri { - pub fn new(uri: Url) -> Self { - Self { uri } - } - - // not ToString to prevent accidental disclosure of credentials - pub fn full_uri(&self) -> String { - self.uri.to_string() - } + pub fn new(uri: Url) -> Self { + Self { uri } + } + + // not ToString to prevent accidental disclosure of credentials + pub fn full_uri(&self) -> String { + self.uri.to_string() + } } -impl std::fmt::Debug for JwksUri { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - fmt, - r#"JwksUri {{ uri: Url {{ scheme: {:?}, cannot_be_a_base: {:?}, username: {:?}, password: ***SECRET***, host: {:?}, port: {:?}, path: {:?}, query: {:?}, fragment: {:?} }} }}"#, - self.uri.scheme(), - self.uri.cannot_be_a_base(), - self.uri.username(), - self.uri.host(), - self.uri.port(), - self.uri.path(), - self.uri.query(), - self.uri.fragment(), - )?; - Ok(()) - } +impl core::fmt::Debug for JwksUri { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + write!( + fmt, + r#"JwksUri {{ uri: Url {{ scheme: {:?}, cannot_be_a_base: {:?}, username: {:?}, password: ***SECRET***, host: {:?}, port: {:?}, path: {:?}, query: {:?}, fragment: {:?} }} }}"#, + self.uri.scheme(), + self.uri.cannot_be_a_base(), + self.uri.username(), + self.uri.host(), + self.uri.port(), + self.uri.path(), + self.uri.query(), + self.uri.fragment(), + )?; + Ok(()) + } } #[derive(Clone)] pub struct UserInfoUri { - uri: Url, + uri: Url, } impl UserInfoUri { - pub fn new(uri: Url) -> Self { - Self { uri } - } - - // not ToString to prevent accidental disclosure of credentials - pub fn full_uri(&self) -> String { - self.uri.to_string() - } + pub fn new(uri: Url) -> Self { + Self { uri } + } + + // not ToString to prevent accidental disclosure of credentials + pub fn full_uri(&self) -> String { + self.uri.to_string() + } } -impl std::fmt::Debug for UserInfoUri { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!( - fmt, - r#"UserInfoUri {{ uri: Url {{ scheme: {:?}, cannot_be_a_base: {:?}, username: {:?}, password: ***SECRET***, host: {:?}, port: {:?}, path: {:?}, query: {:?}, fragment: {:?} }} }}"#, - self.uri.scheme(), - self.uri.cannot_be_a_base(), - self.uri.username(), - self.uri.host(), - self.uri.port(), - self.uri.path(), - self.uri.query(), - self.uri.fragment(), - )?; - Ok(()) - } +impl core::fmt::Debug for UserInfoUri { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + write!( + fmt, + r#"UserInfoUri {{ uri: Url {{ scheme: {:?}, cannot_be_a_base: {:?}, username: {:?}, password: ***SECRET***, host: {:?}, port: {:?}, path: {:?}, query: {:?}, fragment: {:?} }} }}"#, + self.uri.scheme(), + self.uri.cannot_be_a_base(), + self.uri.username(), + self.uri.host(), + self.uri.port(), + self.uri.path(), + self.uri.query(), + self.uri.fragment(), + )?; + Ok(()) + } } pub struct SecurityConf { - jwks_uri: Option, - userinfo_uri: Option, - id_claims: Option>, - jwt_must_claim: HashMap, - allow_anonymous: bool, - opa: ExecutorContext, + jwks_uri: Option, + userinfo_uri: Option, + id_claims: Option>, + jwt_must_claim: HashMap, + allow_anonymous: bool, + opa: ExecutorContext, } impl SecurityConf { - pub fn new( - jwks_uri: Option, - userinfo_uri: Option, - id_claims: Option>, - jwt_must_claim: HashMap, - allow_anonymous: bool, - opa: ExecutorContext, - ) -> Self { - Self { - jwks_uri, - userinfo_uri, - id_claims, - jwt_must_claim, - allow_anonymous, - opa, - } - } + pub fn new( + jwks_uri: Option, + userinfo_uri: Option, + id_claims: Option>, + jwt_must_claim: HashMap, + allow_anonymous: bool, + opa: ExecutorContext, + ) -> Self { + Self { jwks_uri, userinfo_uri, id_claims, jwt_must_claim, allow_anonymous, opa } + } } #[async_trait::async_trait] pub trait ChronicleApiServer { - async fn serve_api( - &self, - pool: Pool>, - api: ApiDispatch, - addresses: Vec, - security_conf: SecurityConf, - serve_graphql: bool, - serve_data: bool, - ) -> Result<(), ApiError>; + async fn serve_api( + &self, + pool: Pool>, + api: ApiDispatch, + addresses: Vec, + security_conf: SecurityConf, + serve_graphql: bool, + serve_data: bool, + ) -> Result<(), ApiError>; } impl ChronicleGraphQl where - Query: ObjectType + Copy, - Mutation: ObjectType + Copy, + Query: ObjectType + Copy, + Mutation: ObjectType + Copy, { - pub fn new(query: Query, mutation: Mutation) -> Self { - Self { query, mutation } - } - - pub fn exportable_schema(&self) -> String - where - Query: ObjectType + Copy, - Mutation: ObjectType + Copy, - { - let schema = Schema::build(self.query, self.mutation, Subscription).finish(); - - schema.sdl() - } + pub fn new(query: Query, mutation: Mutation) -> Self { + Self { query, mutation } + } + + pub fn exportable_schema(&self) -> String + where + Query: ObjectType + Copy, + Mutation: ObjectType + Copy, + { + let schema = Schema::build(self.query, self.mutation, Subscription).finish(); + + schema.sdl() + } } fn check_required_claim(must_value: &str, actual_value: &serde_json::Value) -> bool { - match actual_value { - serde_json::Value::String(actual_value) => must_value == actual_value, - serde_json::Value::Array(actual_values) => actual_values - .iter() - .any(|actual_value| check_required_claim(must_value, actual_value)), - _ => false, - } + match actual_value { + serde_json::Value::String(actual_value) => must_value == actual_value, + serde_json::Value::Array(actual_values) => actual_values + .iter() + .any(|actual_value| check_required_claim(must_value, actual_value)), + _ => false, + } } #[instrument(level = "trace", ret(Debug))] fn check_required_claims( - must_claim: &HashMap, - actual_claims: &serde_json::Map, + must_claim: &HashMap, + actual_claims: &serde_json::Map, ) -> bool { - for (name, value) in must_claim { - if let Some(json) = actual_claims.get(name) { - if !check_required_claim(value, json) { - return false; - } - } else { - return false; - } - } - true + for (name, value) in must_claim { + if let Some(json) = actual_claims.get(name) { + if !check_required_claim(value, json) { + return false; + } + } else { + return false; + } + } + true } async fn check_claims( - secconf: &EndpointSecurityConfiguration, - req: &poem::Request, + secconf: &EndpointSecurityConfiguration, + req: &poem::Request, ) -> Result, poem::Error> { - if let Some(authorization) = req.header("Authorization") { - if let Ok(authorization) = HeaderValue::from_str(authorization) { - let bearer_token_maybe: Option = Credentials::decode(&authorization); - if let Some(bearer_token) = bearer_token_maybe { - if let Ok(claims) = secconf.checker.verify_token(bearer_token.token()).await { - if check_required_claims(&secconf.must_claim, &claims) { - return Ok(Some(JwtClaims(claims))); - } - } - } - } - tracing::trace!( - "rejected authorization from {}: {:?}", - req.remote_addr(), - authorization - ); - Err(poem::error::Error::from_string( - "Authorization header present but without a satisfactory bearer token", - StatusCode::UNAUTHORIZED, - )) - } else if secconf.allow_anonymous { - tracing::trace!("anonymous access from {}", req.remote_addr()); - Ok(None) - } else { - tracing::trace!("rejected anonymous access from {}", req.remote_addr()); - Err(poem::error::Error::from_string( - "required Authorization header not present", - StatusCode::UNAUTHORIZED, - )) - } + if let Some(authorization) = req.header("Authorization") { + if let Ok(authorization) = HeaderValue::from_str(authorization) { + let bearer_token_maybe: Option = Credentials::decode(&authorization); + if let Some(bearer_token) = bearer_token_maybe { + if let Ok(claims) = secconf.checker.verify_token(bearer_token.token()).await { + if check_required_claims(&secconf.must_claim, &claims) { + return Ok(Some(JwtClaims(claims))); + } + } + } + } + tracing::trace!("rejected authorization from {}: {:?}", req.remote_addr(), authorization); + Err(poem::error::Error::from_string( + "Authorization header present but without a satisfactory bearer token", + StatusCode::UNAUTHORIZED, + )) + } else if secconf.allow_anonymous { + tracing::trace!("anonymous access from {}", req.remote_addr()); + Ok(None) + } else { + tracing::trace!("rejected anonymous access from {}", req.remote_addr()); + Err(poem::error::Error::from_string( + "required Authorization header not present", + StatusCode::UNAUTHORIZED, + )) + } } async fn execute_opa_check( - opa_executor: &ExecutorContext, - claim_parser: &Option, - claims: Option<&JwtClaims>, - construct_data: impl FnOnce(&AuthId) -> OpaData, + opa_executor: &ExecutorContext, + claim_parser: &Option, + claims: Option<&JwtClaims>, + construct_data: impl FnOnce(&AuthId) -> OpaData, ) -> Result<(), OpaExecutorError> { - // If unable to get an external_id from the JwtClaims or no claims found, - // identity will be `Anonymous` - let identity = match (claims, claim_parser) { - (Some(claims), Some(parser)) => parser.identity(claims).unwrap_or(AuthId::anonymous()), - _ => AuthId::anonymous(), - }; - - // Create OPA context data for the user identity - let opa_data = construct_data(&identity); - - // Execute OPA check - match opa_executor.evaluate(&identity, &opa_data).await { - Err(error) => { - tracing::warn!( + // If unable to get an external_id from the JwtClaims or no claims found, + // identity will be `Anonymous` + let identity = match (claims, claim_parser) { + (Some(claims), Some(parser)) => parser.identity(claims).unwrap_or(AuthId::anonymous()), + _ => AuthId::anonymous(), + }; + + // Create OPA context data for the user identity + let opa_data = construct_data(&identity); + + // Execute OPA check + match opa_executor.evaluate(&identity, &opa_data).await { + Err(error) => { + tracing::warn!( "{error}: attempt to violate policy rules by identity: {identity}, in context: {:#?}", opa_data ); - Err(error) - } - ok => ok, - } + Err(error) + }, + ok => ok, + } } struct EndpointSecurityConfiguration { - checker: TokenChecker, - must_claim: HashMap, - allow_anonymous: bool, + checker: TokenChecker, + must_claim: HashMap, + allow_anonymous: bool, } impl EndpointSecurityConfiguration { - fn new( - checker: TokenChecker, - must_claim: HashMap, - allow_anonymous: bool, - ) -> Self { - Self { - checker, - must_claim, - allow_anonymous, - } - } - - async fn check_status(&self) -> Result<(), AuthorizationError> { - self.checker.check_status().await - } + fn new( + checker: TokenChecker, + must_claim: HashMap, + allow_anonymous: bool, + ) -> Self { + Self { checker, must_claim, allow_anonymous } + } + + async fn check_status(&self) -> Result<(), AuthorizationError> { + self.checker.check_status().await + } } struct QueryEndpoint { - secconf: EndpointSecurityConfiguration, - schema: Schema, + secconf: EndpointSecurityConfiguration, + schema: Schema, } impl QueryEndpoint where - Q: ObjectType + 'static, - M: ObjectType + 'static, - S: SubscriptionType + 'static, + Q: ObjectType + 'static, + M: ObjectType + 'static, + S: SubscriptionType + 'static, { - #[instrument(level = "debug", skip_all, ret(Debug))] - async fn respond( - &self, - req: poem::Request, - prepare_req: impl FnOnce(GraphQLBatchRequest) -> async_graphql::BatchRequest, - ) -> poem::Result { - use poem::{FromRequest, IntoResponse}; - let (req, mut body) = req.split(); - let req = prepare_req(GraphQLBatchRequest::from_request(&req, &mut body).await?); - Ok(GraphQLBatchResponse(self.schema.execute_batch(req).await).into_response()) - } + #[instrument(level = "debug", skip_all, ret(Debug))] + async fn respond( + &self, + req: poem::Request, + prepare_req: impl FnOnce(GraphQLBatchRequest) -> async_graphql::BatchRequest, + ) -> poem::Result { + use poem::{FromRequest, IntoResponse}; + let (req, mut body) = req.split(); + let req = prepare_req(GraphQLBatchRequest::from_request(&req, &mut body).await?); + Ok(GraphQLBatchResponse(self.schema.execute_batch(req).await).into_response()) + } } #[poem::async_trait] impl Endpoint for QueryEndpoint where - Q: ObjectType + 'static, - M: ObjectType + 'static, - S: SubscriptionType + 'static, + Q: ObjectType + 'static, + M: ObjectType + 'static, + S: SubscriptionType + 'static, { - type Output = poem::Response; - - async fn call(&self, req: poem::Request) -> poem::Result { - let checked_claims = check_claims(&self.secconf, &req).await?; - self.respond(req, |api_req| { - if let Some(claims) = checked_claims { - api_req.0.data(claims) - } else { - api_req.0 - } - }) - .await - } + type Output = poem::Response; + + async fn call(&self, req: poem::Request) -> poem::Result { + let checked_claims = check_claims(&self.secconf, &req).await?; + self.respond(req, |api_req| { + if let Some(claims) = checked_claims { + api_req.0.data(claims) + } else { + api_req.0 + } + }) + .await + } } struct SubscriptionEndpoint { - secconf: EndpointSecurityConfiguration, - schema: Schema, + secconf: EndpointSecurityConfiguration, + schema: Schema, } impl SubscriptionEndpoint where - Q: ObjectType + 'static, - M: ObjectType + 'static, - S: SubscriptionType + 'static, + Q: ObjectType + 'static, + M: ObjectType + 'static, + S: SubscriptionType + 'static, { - #[instrument(level = "trace", skip(self, req), ret(Debug))] - async fn respond( - &self, - req: poem::Request, - data: async_graphql::Data, - ) -> poem::Result { - use poem::{FromRequest, IntoResponse}; - let (req, mut body) = req.split(); - let websocket = poem::web::websocket::WebSocket::from_request(&req, &mut body).await?; - let protocol = GraphQLProtocol::from_request(&req, &mut body).await?; - let schema = self.schema.clone(); - Ok(websocket - .protocols(ALL_WEBSOCKET_PROTOCOLS) - .on_upgrade(move |stream| { - GraphQLWebSocket::new(stream, schema, protocol) - .with_data(data) - .serve() - }) - .into_response()) - } + #[instrument(level = "trace", skip(self, req), ret(Debug))] + async fn respond( + &self, + req: poem::Request, + data: async_graphql::Data, + ) -> poem::Result { + use poem::{FromRequest, IntoResponse}; + let (req, mut body) = req.split(); + let websocket = poem::web::websocket::WebSocket::from_request(&req, &mut body).await?; + let protocol = GraphQLProtocol::from_request(&req, &mut body).await?; + let schema = self.schema.clone(); + Ok(websocket + .protocols(ALL_WEBSOCKET_PROTOCOLS) + .on_upgrade(move |stream| { + GraphQLWebSocket::new(stream, schema, protocol).with_data(data).serve() + }) + .into_response()) + } } #[poem::async_trait] impl Endpoint for SubscriptionEndpoint where - Q: ObjectType + 'static, - M: ObjectType + 'static, - S: SubscriptionType + 'static, + Q: ObjectType + 'static, + M: ObjectType + 'static, + S: SubscriptionType + 'static, { - type Output = poem::Response; - - async fn call(&self, req: poem::Request) -> poem::Result { - let checked_claims = check_claims(&self.secconf, &req).await?; - self.respond( - req, - if let Some(claims) = checked_claims { - let mut data = async_graphql::Data::default(); - data.insert(claims); - data - } else { - async_graphql::Data::default() - }, - ) - .await - } + type Output = poem::Response; + + async fn call(&self, req: poem::Request) -> poem::Result { + let checked_claims = check_claims(&self.secconf, &req).await?; + self.respond( + req, + if let Some(claims) = checked_claims { + let mut data = async_graphql::Data::default(); + data.insert(claims); + data + } else { + async_graphql::Data::default() + }, + ) + .await + } } struct IriEndpoint { - secconf: Option, - store: super::persistence::Store, - opa_executor: ExecutorContext, - claim_parser: Option, + secconf: Option, + store: super::persistence::Store, + opa_executor: ExecutorContext, + claim_parser: Option, } impl IriEndpoint { - async fn response_for_query( - &self, - claims: Option<&JwtClaims>, - prov_type: &str, - id: &ID, - ns: &ExternalId, - retrieve: impl FnOnce( - PooledConnection>, - &ID, - &ExternalId, - ) -> Result, - ) -> poem::Result { - match execute_opa_check(&self.opa_executor, &self.claim_parser, claims, |identity| { - OpaData::operation( - identity, - &json!("ReadData"), - &json!({ - "type": prov_type, - "id": id.external_id_part(), - "namespace": ns - }), - ) - }) - .await - { - Ok(()) => match self.store.connection() { - Ok(connection) => match retrieve(connection, id, ns) { - Ok(data) => match data.to_json().compact().await { - Ok(mut json) => { - use serde_json::Value; - if let Value::Object(mut map) = json { - map.insert( - "@context".to_string(), - Value::String("/context".to_string()), - ); - json = Value::Object(map); - } - Ok(IntoResponse::into_response(poem::web::Json(json))) - } - Err(error) => { - tracing::error!("JSON failed compaction: {error}"); - Ok(poem::Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body("failed to compact JSON response")) - } - }, - Err(StoreError::Db(diesel::result::Error::NotFound)) - | Err(StoreError::RecordNotFound) => { - tracing::debug!("not found: {prov_type} {} in {ns}", id.external_id_part()); - Ok(poem::Response::builder() - .status(StatusCode::NOT_FOUND) - .body(format!("the specified {prov_type} does not exist"))) - } - Err(error) => { - tracing::error!("failed to retrieve from database: {error}"); - Ok(poem::Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body("failed to fetch from backend storage")) - } - }, - Err(error) => { - tracing::error!("failed to connect to database: {error}"); - Ok(poem::Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body("failed to access backend storage")) - } - }, - Err(_) => Ok(poem::Response::builder() - .status(StatusCode::FORBIDDEN) - .body("violation of policy rules")), - } - } - - async fn parse_ns_iri_from_uri_path( - &self, - req: poem::Request, - ) -> poem::Result> { - use poem::{web::Path, FromRequest, Response}; - - #[derive(Clone, Debug, Serialize, Deserialize)] - struct NamespacedIri { - ns: String, - iri: String, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - struct Iri { - iri: String, - } - - impl From for NamespacedIri { - fn from(value: Iri) -> Self { - NamespacedIri { - ns: "default".to_string(), - iri: value.iri, - } - } - } - - let (req, mut body) = req.split(); - - let ns_iri: poem::Result> = - FromRequest::from_request(&req, &mut body).await; - - let ns_iri: NamespacedIri = match ns_iri { - Ok(Path(nsi)) => nsi, - Err(_) => { - let path: Path = FromRequest::from_request(&req, &mut body).await?; - path.0.into() - } - }; - - match ChronicleIri::from_str(&ns_iri.iri) { - Ok(iri) => Ok(Ok((ns_iri.ns.into(), iri))), - Err(error) => Ok(Err(Response::builder() - .status(StatusCode::NOT_FOUND) - .body(error.to_string()))), - } - } - - #[instrument(level = "trace", skip(self, req), ret(Debug))] - async fn respond( - &self, - req: poem::Request, - claims: Option<&JwtClaims>, - ) -> poem::Result { - match self.parse_ns_iri_from_uri_path(req).await? { - Ok((ns, ChronicleIri::Activity(id))) => { - self.response_for_query(claims, "activity", &id, &ns, |mut conn, id, ns| { - self.store.prov_model_for_activity_id(&mut conn, id, ns) - }) - .await - } - Ok((ns, ChronicleIri::Agent(id))) => { - self.response_for_query(claims, "agent", &id, &ns, |mut conn, id, ns| { - self.store.prov_model_for_agent_id(&mut conn, id, ns) - }) - .await - } - Ok((ns, ChronicleIri::Entity(id))) => { - self.response_for_query(claims, "entity", &id, &ns, |mut conn, id, ns| { - self.store.prov_model_for_entity_id(&mut conn, id, ns) - }) - .await - } - Ok(_) => Ok(poem::Response::builder() - .status(StatusCode::NOT_FOUND) - .body("may query only: activity, agent, entity")), - Err(rsp) => Ok(rsp), - } - } + async fn response_for_query( + &self, + claims: Option<&JwtClaims>, + prov_type: &str, + id: &ID, + ns: &ExternalId, + retrieve: impl FnOnce( + PooledConnection>, + &ID, + &ExternalId, + ) -> Result, + ) -> poem::Result { + match execute_opa_check(&self.opa_executor, &self.claim_parser, claims, |identity| { + OpaData::operation( + identity, + &json!("ReadData"), + &json!({ + "type": prov_type, + "id": id.external_id_part(), + "namespace": ns + }), + ) + }) + .await + { + Ok(()) => match self.store.connection() { + Ok(connection) => match retrieve(connection, id, ns) { + Ok(data) => match data.to_json().compact().await { + Ok(mut json) => { + use serde_json::Value; + if let Value::Object(mut map) = json { + map.insert( + "@context".to_string(), + Value::String("/context".to_string()), + ); + json = Value::Object(map); + } + Ok(IntoResponse::into_response(poem::web::Json(json))) + }, + Err(error) => { + tracing::error!("JSON failed compaction: {error}"); + Ok(poem::Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body("failed to compact JSON response")) + }, + }, + Err(StoreError::Db(diesel::result::Error::NotFound)) + | Err(StoreError::RecordNotFound) => { + tracing::debug!("not found: {prov_type} {} in {ns}", id.external_id_part()); + Ok(poem::Response::builder() + .status(StatusCode::NOT_FOUND) + .body(format!("the specified {prov_type} does not exist"))) + }, + Err(error) => { + tracing::error!("failed to retrieve from database: {error}"); + Ok(poem::Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body("failed to fetch from backend storage")) + }, + }, + Err(error) => { + tracing::error!("failed to connect to database: {error}"); + Ok(poem::Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body("failed to access backend storage")) + }, + }, + Err(_) => Ok(poem::Response::builder() + .status(StatusCode::FORBIDDEN) + .body("violation of policy rules")), + } + } + + async fn parse_ns_iri_from_uri_path( + &self, + req: poem::Request, + ) -> poem::Result> { + use poem::{web::Path, FromRequest, Response}; + + #[derive(Clone, Debug, Serialize, Deserialize)] + struct NamespacedIri { + ns: String, + iri: String, + } + + #[derive(Clone, Debug, Serialize, Deserialize)] + struct Iri { + iri: String, + } + + impl From for NamespacedIri { + fn from(value: Iri) -> Self { + NamespacedIri { ns: "default".to_string(), iri: value.iri } + } + } + + let (req, mut body) = req.split(); + + let ns_iri: poem::Result> = + FromRequest::from_request(&req, &mut body).await; + + let ns_iri: NamespacedIri = match ns_iri { + Ok(Path(nsi)) => nsi, + Err(_) => { + let path: Path = FromRequest::from_request(&req, &mut body).await?; + path.0.into() + }, + }; + + match ChronicleIri::from_str(&ns_iri.iri) { + Ok(iri) => Ok(Ok((ns_iri.ns.into(), iri))), + Err(error) => { + Ok(Err(Response::builder().status(StatusCode::NOT_FOUND).body(error.to_string()))) + }, + } + } + + #[instrument(level = "trace", skip(self, req), ret(Debug))] + async fn respond( + &self, + req: poem::Request, + claims: Option<&JwtClaims>, + ) -> poem::Result { + match self.parse_ns_iri_from_uri_path(req).await? { + Ok((ns, ChronicleIri::Activity(id))) => { + self.response_for_query(claims, "activity", &id, &ns, |mut conn, id, ns| { + self.store.prov_model_for_activity_id(&mut conn, id, ns) + }) + .await + }, + Ok((ns, ChronicleIri::Agent(id))) => { + self.response_for_query(claims, "agent", &id, &ns, |mut conn, id, ns| { + self.store.prov_model_for_agent_id(&mut conn, id, ns) + }) + .await + }, + Ok((ns, ChronicleIri::Entity(id))) => { + self.response_for_query(claims, "entity", &id, &ns, |mut conn, id, ns| { + self.store.prov_model_for_entity_id(&mut conn, id, ns) + }) + .await + }, + Ok(_) => Ok(poem::Response::builder() + .status(StatusCode::NOT_FOUND) + .body("may query only: activity, agent, entity")), + Err(rsp) => Ok(rsp), + } + } } #[poem::async_trait] impl Endpoint for IriEndpoint { - type Output = poem::Response; - - async fn call(&self, req: poem::Request) -> poem::Result { - let checked_claims = if let Some(secconf) = &self.secconf { - check_claims(secconf, &req).await? - } else { - None - }; - self.respond(req, checked_claims.as_ref()).await - } + type Output = poem::Response; + + async fn call(&self, req: poem::Request) -> poem::Result { + let checked_claims = if let Some(secconf) = &self.secconf { + check_claims(secconf, &req).await? + } else { + None + }; + self.respond(req, checked_claims.as_ref()).await + } } struct LdContextEndpoint; #[poem::async_trait] impl Endpoint for LdContextEndpoint { - type Output = poem::Response; + type Output = poem::Response; - async fn call(&self, _req: poem::Request) -> poem::Result { - let context: &serde_json::Value = &common::context::PROV; - Ok(IntoResponse::into_response(poem::web::Json(context))) - } + async fn call(&self, _req: poem::Request) -> poem::Result { + let context: &serde_json::Value = &common::context::PROV; + Ok(IntoResponse::into_response(poem::web::Json(context))) + } } #[derive(Clone, Debug)] pub struct AuthFromJwt { - id_claims: BTreeSet, - allow_anonymous: bool, + id_claims: BTreeSet, + allow_anonymous: bool, } impl AuthFromJwt { - #[instrument(level = "debug", ret(Debug))] - fn identity(&self, claims: &JwtClaims) -> Result { - AuthId::from_jwt_claims(claims, &self.id_claims) - } + #[instrument(level = "debug", ret(Debug))] + fn identity(&self, claims: &JwtClaims) -> Result { + AuthId::from_jwt_claims(claims, &self.id_claims) + } } #[async_trait::async_trait] impl async_graphql::extensions::Extension for AuthFromJwt { - async fn prepare_request( - &self, - ctx: &async_graphql::extensions::ExtensionContext<'_>, - mut request: async_graphql::Request, - next: async_graphql::extensions::NextPrepareRequest<'_>, - ) -> async_graphql::ServerResult { - if let Some(claims) = ctx.data_opt::() { - match self.identity(claims) { - Ok(chronicle_id) => request = request.data(chronicle_id), - Err(error) if self.allow_anonymous => { - debug!("Identity could not be determined: {:?}", error) - } - Err(error) => { - warn!( - "Rejecting request because required identity could not be determined: {:?}", - error - ); - return Err(ServerError::new("Authorization header present but identity could not be determined from bearer token", None)); - } - } - } - next.run(ctx, request).await - } + async fn prepare_request( + &self, + ctx: &async_graphql::extensions::ExtensionContext<'_>, + mut request: async_graphql::Request, + next: async_graphql::extensions::NextPrepareRequest<'_>, + ) -> async_graphql::ServerResult { + if let Some(claims) = ctx.data_opt::() { + match self.identity(claims) { + Ok(chronicle_id) => request = request.data(chronicle_id), + Err(error) if self.allow_anonymous => { + debug!("Identity could not be determined: {:?}", error) + }, + Err(error) => { + warn!( + "Rejecting request because required identity could not be determined: {:?}", + error + ); + return Err(ServerError::new("Authorization header present but identity could not be determined from bearer token", None)); + }, + } + } + next.run(ctx, request).await + } } #[async_trait::async_trait] impl async_graphql::extensions::ExtensionFactory for AuthFromJwt { - fn create(&self) -> Arc { - Arc::new(AuthFromJwt { - id_claims: self.id_claims.clone(), - allow_anonymous: self.allow_anonymous, - }) - } + fn create(&self) -> Arc { + Arc::new(AuthFromJwt { + id_claims: self.id_claims.clone(), + allow_anonymous: self.allow_anonymous, + }) + } } #[derive(Clone, Debug)] pub struct OpaCheck { - pub claim_parser: Option, + pub claim_parser: Option, } #[async_trait::async_trait] impl async_graphql::extensions::Extension for OpaCheck { - #[instrument(level = "trace", skip_all, ret(Debug))] - async fn resolve( - &self, - ctx: &async_graphql::extensions::ExtensionContext<'_>, - info: async_graphql::extensions::ResolveInfo<'_>, - next: async_graphql::extensions::NextResolve<'_>, - ) -> async_graphql::ServerResult> { - use async_graphql::ServerError; - use serde_json::Value; - if let Some(opa_executor) = ctx.data_opt::() { - match execute_opa_check( - opa_executor, - &self.claim_parser, - ctx.data_opt::(), - |identity| { - OpaData::graphql( - identity, - &Value::String(info.parent_type.to_string()), - &Value::Array( - info.path_node - .to_string_vec() - .into_iter() - .map(Value::String) - .collect(), - ), - ) - }, - ) - .await - { - Ok(()) => next.run(ctx, info).await, - Err(_) => Err(ServerError::new("violation of policy rules", None)), - } - } else { - Err(ServerError::new("cannot check policy rules", None)) - } - } + #[instrument(level = "trace", skip_all, ret(Debug))] + async fn resolve( + &self, + ctx: &async_graphql::extensions::ExtensionContext<'_>, + info: async_graphql::extensions::ResolveInfo<'_>, + next: async_graphql::extensions::NextResolve<'_>, + ) -> async_graphql::ServerResult> { + use async_graphql::ServerError; + use serde_json::Value; + if let Some(opa_executor) = ctx.data_opt::() { + match execute_opa_check( + opa_executor, + &self.claim_parser, + ctx.data_opt::(), + |identity| { + OpaData::graphql( + identity, + &Value::String(info.parent_type.to_string()), + &Value::Array( + info.path_node.to_string_vec().into_iter().map(Value::String).collect(), + ), + ) + }, + ) + .await + { + Ok(()) => next.run(ctx, info).await, + Err(_) => Err(ServerError::new("violation of policy rules", None)), + } + } else { + Err(ServerError::new("cannot check policy rules", None)) + } + } } #[async_trait::async_trait] impl async_graphql::extensions::ExtensionFactory for OpaCheck { - fn create(&self) -> Arc { - Arc::new(OpaCheck { - claim_parser: self.claim_parser.clone(), - }) - } + fn create(&self) -> Arc { + Arc::new(OpaCheck { claim_parser: self.claim_parser.clone() }) + } } lazy_static! { - static ref SHUTDOWN_SIGNAL: Arc = Arc::new(Semaphore::new(0)); + static ref SHUTDOWN_SIGNAL: Arc = Arc::new(Semaphore::new(0)); } fn trigger_shutdown() { - SHUTDOWN_SIGNAL.add_permits(1); + SHUTDOWN_SIGNAL.add_permits(1); } async fn await_shutdown() { - let _permit = SHUTDOWN_SIGNAL.acquire().await.unwrap(); + let _permit = SHUTDOWN_SIGNAL.acquire().await.unwrap(); } #[async_trait::async_trait] impl ChronicleApiServer for ChronicleGraphQl where - Query: ObjectType + Copy, - Mutation: ObjectType + Copy, + Query: ObjectType + Copy, + Mutation: ObjectType + Copy, { - async fn serve_api( - &self, - pool: Pool>, - api: ApiDispatch, - addresses: Vec, - sec: SecurityConf, - serve_graphql: bool, - serve_data: bool, - ) -> Result<(), ApiError> { - let claim_parser = sec.id_claims.map(|id_claims| AuthFromJwt { - id_claims, - allow_anonymous: sec.allow_anonymous, - }); - let mut schema = Schema::build(self.query, self.mutation, Subscription) - .extension(OpenTelemetry::new(opentelemetry::global::tracer( - "chronicle-api-gql", - ))) - .extension(OpaCheck { - claim_parser: claim_parser.clone(), - }); - if let Some(claim_parser) = &claim_parser { - schema = schema.extension(claim_parser.clone()); - } - let schema = schema - .data(Store::new(pool.clone())) - .data(api) - .data(sec.opa.clone()) - .data(AuthId::anonymous()) - .finish(); - - let iri_endpoint = |secconf| IriEndpoint { - secconf, - store: super::persistence::Store::new(pool.clone()).unwrap(), - opa_executor: sec.opa.clone(), - claim_parser: claim_parser.clone(), - }; - - let mut app = Route::new(); - - match (&sec.jwks_uri, &sec.userinfo_uri) { - (None, None) => { - tracing::warn!("API endpoint uses no authentication"); - - if serve_graphql { - app = app - .at("/", get(gql_playground).post(GraphQL::new(schema.clone()))) - .at("/ws", get(GraphQLSubscription::new(schema))) - }; - if serve_data { - app = app - .at("/context", get(LdContextEndpoint)) - .at("/data/:iri", get(iri_endpoint(None))) - .at("/data/:ns/:iri", get(iri_endpoint(None))) - }; - } - (jwks_uri, userinfo_uri) => { - const CACHE_EXPIRY_SECONDS: u32 = 100; - if let Some(uri) = jwks_uri { - tracing::debug!(oidc_jwks_endpoint = ?uri); - } - if let Some(uri) = userinfo_uri { - tracing::debug!(oidc_userinfo_endpoint = ?uri); - } - - let secconf = || { - EndpointSecurityConfiguration::new( - TokenChecker::new( - jwks_uri.as_ref(), - userinfo_uri.as_ref(), - CACHE_EXPIRY_SECONDS, - ), - sec.jwt_must_claim.clone(), - sec.allow_anonymous, - ) - }; - - secconf().check_status().await?; - - if serve_graphql { - app = app - .at( - "/", - post(QueryEndpoint { - secconf: secconf(), - schema: schema.clone(), - }), - ) - .at( - "/ws", - get(SubscriptionEndpoint { - secconf: secconf(), - schema, - }), - ) - }; - if serve_data { - app = app - .at("/context", get(LdContextEndpoint)) - .at("/data/:iri", get(iri_endpoint(Some(secconf())))) - .at("/data/:ns/:iri", get(iri_endpoint(Some(secconf())))) - }; - } - } - - let listener = addresses - .into_iter() - .map(|address| TcpListener::bind(address).boxed()) - .reduce(|listener_1, listener_2| listener_1.combine(listener_2).boxed()) - .unwrap(); - - Server::new(listener) - .run_with_graceful_shutdown(app, await_shutdown(), None) - .await?; - - Ok(()) - } + async fn serve_api( + &self, + pool: Pool>, + api: ApiDispatch, + addresses: Vec, + sec: SecurityConf, + serve_graphql: bool, + serve_data: bool, + ) -> Result<(), ApiError> { + let claim_parser = sec + .id_claims + .map(|id_claims| AuthFromJwt { id_claims, allow_anonymous: sec.allow_anonymous }); + let mut schema = Schema::build(self.query, self.mutation, Subscription) + .extension(OpenTelemetry::new(opentelemetry::global::tracer("chronicle-api-gql"))) + .extension(OpaCheck { claim_parser: claim_parser.clone() }); + if let Some(claim_parser) = &claim_parser { + schema = schema.extension(claim_parser.clone()); + } + let schema = schema + .data(Store::new(pool.clone())) + .data(api) + .data(sec.opa.clone()) + .data(AuthId::anonymous()) + .finish(); + + let iri_endpoint = |secconf| IriEndpoint { + secconf, + store: super::persistence::Store::new(pool.clone()).unwrap(), + opa_executor: sec.opa.clone(), + claim_parser: claim_parser.clone(), + }; + + let mut app = Route::new(); + + match (&sec.jwks_uri, &sec.userinfo_uri) { + (None, None) => { + tracing::warn!("API endpoint uses no authentication"); + + if serve_graphql { + app = app + .at("/", get(gql_playground).post(GraphQL::new(schema.clone()))) + .at("/ws", get(GraphQLSubscription::new(schema))) + }; + if serve_data { + app = app + .at("/context", get(LdContextEndpoint)) + .at("/data/:iri", get(iri_endpoint(None))) + .at("/data/:ns/:iri", get(iri_endpoint(None))) + }; + }, + (jwks_uri, userinfo_uri) => { + const CACHE_EXPIRY_SECONDS: u32 = 100; + if let Some(uri) = jwks_uri { + tracing::debug!(oidc_jwks_endpoint = ?uri); + } + if let Some(uri) = userinfo_uri { + tracing::debug!(oidc_userinfo_endpoint = ?uri); + } + + let secconf = || { + EndpointSecurityConfiguration::new( + TokenChecker::new( + jwks_uri.as_ref(), + userinfo_uri.as_ref(), + CACHE_EXPIRY_SECONDS, + ), + sec.jwt_must_claim.clone(), + sec.allow_anonymous, + ) + }; + + secconf().check_status().await?; + + if serve_graphql { + app = app + .at("/", post(QueryEndpoint { secconf: secconf(), schema: schema.clone() })) + .at("/ws", get(SubscriptionEndpoint { secconf: secconf(), schema })) + }; + if serve_data { + app = app + .at("/context", get(LdContextEndpoint)) + .at("/data/:iri", get(iri_endpoint(Some(secconf())))) + .at("/data/:ns/:iri", get(iri_endpoint(Some(secconf())))) + }; + }, + } + + let listener = addresses + .into_iter() + .map(|address| TcpListener::bind(address).boxed()) + .reduce(|listener_1, listener_2| listener_1.combine(listener_2).boxed()) + .unwrap(); + + Server::new(listener) + .run_with_graceful_shutdown(app, await_shutdown(), None) + .await?; + + Ok(()) + } } diff --git a/crates/api/src/chronicle_graphql/mutation.rs b/crates/api/src/chronicle_graphql/mutation.rs index f58510aab..43c806858 100644 --- a/crates/api/src/chronicle_graphql/mutation.rs +++ b/crates/api/src/chronicle_graphql/mutation.rs @@ -3,443 +3,385 @@ use async_graphql::Context; use chrono::{DateTime, Utc}; use common::{ - attributes::Attributes, - commands::{ActivityCommand, AgentCommand, ApiCommand, ApiResponse, EntityCommand}, - identity::AuthId, - prov::{operations::DerivationType, ActivityId, AgentId, EntityId, Role}, + attributes::Attributes, + commands::{ActivityCommand, AgentCommand, ApiCommand, ApiResponse, EntityCommand}, + identity::AuthId, + prov::{operations::DerivationType, ActivityId, AgentId, EntityId, Role}, }; use crate::ApiDispatch; use super::Submission; async fn transaction_context<'a>( - res: ApiResponse, - _ctx: &Context<'a>, + res: ApiResponse, + _ctx: &Context<'a>, ) -> async_graphql::Result { - match res { - ApiResponse::Submission { subject, tx_id, .. } => { - Ok(Submission::from_submission(&subject, &tx_id)) - } - ApiResponse::AlreadyRecorded { subject, .. } => { - Ok(Submission::from_already_recorded(&subject)) - } - _ => unreachable!(), - } + match res { + ApiResponse::Submission { subject, tx_id, .. } => + Ok(Submission::from_submission(&subject, &tx_id)), + ApiResponse::AlreadyRecorded { subject, .. } => + Ok(Submission::from_already_recorded(&subject)), + _ => unreachable!(), + } } async fn derivation<'a>( - ctx: &Context<'a>, - namespace: Option, - generated_entity: EntityId, - used_entity: EntityId, - derivation: DerivationType, + ctx: &Context<'a>, + namespace: Option, + generated_entity: EntityId, + used_entity: EntityId, + derivation: DerivationType, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); - - let identity = ctx.data_unchecked::().to_owned(); - - let namespace = namespace.unwrap_or_else(|| "default".into()).into(); - - let res = api - .dispatch( - ApiCommand::Entity(EntityCommand::Derive { - id: generated_entity, - namespace, - activity: None, - used_entity, - derivation, - }), - identity, - ) - .await?; - - transaction_context(res, ctx).await + let api = ctx.data_unchecked::(); + + let identity = ctx.data_unchecked::().to_owned(); + + let namespace = namespace.unwrap_or_else(|| "default".into()).into(); + + let res = api + .dispatch( + ApiCommand::Entity(EntityCommand::Derive { + id: generated_entity, + namespace, + activity: None, + used_entity, + derivation, + }), + identity, + ) + .await?; + + transaction_context(res, ctx).await } pub async fn agent<'a>( - ctx: &Context<'a>, - external_id: String, - namespace: Option, - attributes: Attributes, + ctx: &Context<'a>, + external_id: String, + namespace: Option, + attributes: Attributes, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()); - let res = api - .dispatch( - ApiCommand::Agent(AgentCommand::Create { - external_id: external_id.into(), - namespace: namespace.into(), - attributes, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Agent(AgentCommand::Create { + external_id: external_id.into(), + namespace: namespace.into(), + attributes, + }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } pub async fn activity<'a>( - ctx: &Context<'a>, - external_id: String, - namespace: Option, - attributes: Attributes, + ctx: &Context<'a>, + external_id: String, + namespace: Option, + attributes: Attributes, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()); - let res = api - .dispatch( - ApiCommand::Activity(ActivityCommand::Create { - external_id: external_id.into(), - namespace: namespace.into(), - attributes, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Activity(ActivityCommand::Create { + external_id: external_id.into(), + namespace: namespace.into(), + attributes, + }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } pub async fn entity<'a>( - ctx: &Context<'a>, - external_id: String, - namespace: Option, - attributes: Attributes, + ctx: &Context<'a>, + external_id: String, + namespace: Option, + attributes: Attributes, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()); - let res = api - .dispatch( - ApiCommand::Entity(EntityCommand::Create { - external_id: external_id.into(), - namespace: namespace.into(), - attributes, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Entity(EntityCommand::Create { + external_id: external_id.into(), + namespace: namespace.into(), + attributes, + }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } pub async fn acted_on_behalf_of<'a>( - ctx: &Context<'a>, - namespace: Option, - responsible_id: AgentId, - delegate_id: AgentId, - activity_id: Option, - role: Option, + ctx: &Context<'a>, + namespace: Option, + responsible_id: AgentId, + delegate_id: AgentId, + activity_id: Option, + role: Option, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); - - let identity = ctx.data_unchecked::().to_owned(); - - let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); - - let res = api - .dispatch( - ApiCommand::Agent(AgentCommand::Delegate { - id: responsible_id, - delegate: delegate_id, - activity: activity_id, - namespace, - role, - }), - identity, - ) - .await?; - - transaction_context(res, ctx).await + let api = ctx.data_unchecked::(); + + let identity = ctx.data_unchecked::().to_owned(); + + let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); + + let res = api + .dispatch( + ApiCommand::Agent(AgentCommand::Delegate { + id: responsible_id, + delegate: delegate_id, + activity: activity_id, + namespace, + role, + }), + identity, + ) + .await?; + + transaction_context(res, ctx).await } pub async fn was_derived_from<'a>( - ctx: &Context<'a>, - namespace: Option, - generated_entity: EntityId, - used_entity: EntityId, + ctx: &Context<'a>, + namespace: Option, + generated_entity: EntityId, + used_entity: EntityId, ) -> async_graphql::Result { - derivation( - ctx, - namespace, - generated_entity, - used_entity, - DerivationType::None, - ) - .await + derivation(ctx, namespace, generated_entity, used_entity, DerivationType::None).await } pub async fn was_revision_of<'a>( - ctx: &Context<'a>, - namespace: Option, - generated_entity: EntityId, - used_entity: EntityId, + ctx: &Context<'a>, + namespace: Option, + generated_entity: EntityId, + used_entity: EntityId, ) -> async_graphql::Result { - derivation( - ctx, - namespace, - generated_entity, - used_entity, - DerivationType::Revision, - ) - .await + derivation(ctx, namespace, generated_entity, used_entity, DerivationType::Revision).await } pub async fn had_primary_source<'a>( - ctx: &Context<'a>, - namespace: Option, - generated_entity: EntityId, - used_entity: EntityId, + ctx: &Context<'a>, + namespace: Option, + generated_entity: EntityId, + used_entity: EntityId, ) -> async_graphql::Result { - derivation( - ctx, - namespace, - generated_entity, - used_entity, - DerivationType::PrimarySource, - ) - .await + derivation(ctx, namespace, generated_entity, used_entity, DerivationType::PrimarySource).await } pub async fn was_quoted_from<'a>( - ctx: &Context<'a>, - namespace: Option, - generated_entity: EntityId, - used_entity: EntityId, + ctx: &Context<'a>, + namespace: Option, + generated_entity: EntityId, + used_entity: EntityId, ) -> async_graphql::Result { - derivation( - ctx, - namespace, - generated_entity, - used_entity, - DerivationType::Quotation, - ) - .await + derivation(ctx, namespace, generated_entity, used_entity, DerivationType::Quotation).await } pub async fn start_activity<'a>( - ctx: &Context<'a>, - id: ActivityId, - namespace: Option, - agent: Option, // deprecated, slated for removal in CHRON-185 - time: Option>, + ctx: &Context<'a>, + id: ActivityId, + namespace: Option, + agent: Option, // deprecated, slated for removal in CHRON-185 + time: Option>, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); - let res = api - .dispatch( - ApiCommand::Activity(ActivityCommand::Start { - id, - namespace, - time, - agent, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Activity(ActivityCommand::Start { id, namespace, time, agent }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } pub async fn end_activity<'a>( - ctx: &Context<'a>, - id: ActivityId, - namespace: Option, - agent: Option, // deprecated, slated for removal in CHRON-185 - time: Option>, + ctx: &Context<'a>, + id: ActivityId, + namespace: Option, + agent: Option, // deprecated, slated for removal in CHRON-185 + time: Option>, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); - let res = api - .dispatch( - ApiCommand::Activity(ActivityCommand::End { - id, - namespace, - time, - agent, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Activity(ActivityCommand::End { id, namespace, time, agent }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } pub async fn instant_activity<'a>( - ctx: &Context<'a>, - id: ActivityId, - namespace: Option, - agent: Option, // deprecated, slated for removal in CHRON-185 - time: Option>, + ctx: &Context<'a>, + id: ActivityId, + namespace: Option, + agent: Option, // deprecated, slated for removal in CHRON-185 + time: Option>, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); - let res = api - .dispatch( - ApiCommand::Activity(ActivityCommand::Instant { - id, - namespace, - time, - agent, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Activity(ActivityCommand::Instant { id, namespace, time, agent }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } pub async fn was_associated_with<'a>( - ctx: &Context<'a>, - namespace: Option, - responsible: AgentId, - activity: ActivityId, - role: Option, + ctx: &Context<'a>, + namespace: Option, + responsible: AgentId, + activity: ActivityId, + role: Option, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); - let res = api - .dispatch( - ApiCommand::Activity(ActivityCommand::Associate { - id: activity, - responsible, - role, - namespace, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Activity(ActivityCommand::Associate { + id: activity, + responsible, + role, + namespace, + }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } pub async fn was_attributed_to<'a>( - ctx: &Context<'a>, - namespace: Option, - responsible: AgentId, - id: EntityId, - role: Option, + ctx: &Context<'a>, + namespace: Option, + responsible: AgentId, + id: EntityId, + role: Option, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); - let res = api - .dispatch( - ApiCommand::Entity(EntityCommand::Attribute { - id, - namespace, - responsible, - role, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Entity(EntityCommand::Attribute { id, namespace, responsible, role }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } pub async fn used<'a>( - ctx: &Context<'a>, - activity: ActivityId, - entity: EntityId, - namespace: Option, + ctx: &Context<'a>, + activity: ActivityId, + entity: EntityId, + namespace: Option, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); - let res = api - .dispatch( - ApiCommand::Activity(ActivityCommand::Use { - id: entity, - namespace, - activity, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Activity(ActivityCommand::Use { id: entity, namespace, activity }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } pub async fn was_informed_by<'a>( - ctx: &Context<'a>, - activity: ActivityId, - informing_activity: ActivityId, - namespace: Option, + ctx: &Context<'a>, + activity: ActivityId, + informing_activity: ActivityId, + namespace: Option, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); - let res = api - .dispatch( - ApiCommand::Activity(ActivityCommand::WasInformedBy { - id: activity, - namespace, - informing_activity, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Activity(ActivityCommand::WasInformedBy { + id: activity, + namespace, + informing_activity, + }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } pub async fn was_generated_by<'a>( - ctx: &Context<'a>, - activity: ActivityId, - entity: EntityId, - namespace: Option, + ctx: &Context<'a>, + activity: ActivityId, + entity: EntityId, + namespace: Option, ) -> async_graphql::Result { - let api = ctx.data_unchecked::(); + let api = ctx.data_unchecked::(); - let identity = ctx.data_unchecked::().to_owned(); + let identity = ctx.data_unchecked::().to_owned(); - let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); + let namespace = namespace.unwrap_or_else(|| "default".to_owned()).into(); - let res = api - .dispatch( - ApiCommand::Activity(ActivityCommand::Generate { - id: entity, - namespace, - activity, - }), - identity, - ) - .await?; + let res = api + .dispatch( + ApiCommand::Activity(ActivityCommand::Generate { id: entity, namespace, activity }), + identity, + ) + .await?; - transaction_context(res, ctx).await + transaction_context(res, ctx).await } diff --git a/crates/api/src/chronicle_graphql/query.rs b/crates/api/src/chronicle_graphql/query.rs index 766b51e2f..e98a302e6 100644 --- a/crates/api/src/chronicle_graphql/query.rs +++ b/crates/api/src/chronicle_graphql/query.rs @@ -1,14 +1,14 @@ use async_graphql::{ - connection::{query, Connection, EmptyFields}, - Context, ID, + connection::{query, Connection, EmptyFields}, + Context, ID, }; use chrono::{DateTime, NaiveDate, TimeZone, Utc}; use diesel::{debug_query, pg::Pg, prelude::*}; use tracing::{debug, instrument}; use super::{ - cursor_query::{project_to_nodes, Cursorize}, - Activity, Agent, Entity, GraphQlError, Store, TimelineOrder, + cursor_query::{project_to_nodes, Cursorize}, + Activity, Agent, Entity, GraphQlError, Store, TimelineOrder, }; use crate::persistence::schema::generation; use common::prov::{ActivityId, AgentId, DomaintypeId, EntityId, ExternalIdPart}; @@ -16,366 +16,302 @@ use common::prov::{ActivityId, AgentId, DomaintypeId, EntityId, ExternalIdPart}; #[allow(clippy::too_many_arguments)] #[instrument(skip(ctx))] pub async fn activity_timeline<'a>( - ctx: &Context<'a>, - activity_types: Option>, - for_agent: Option>, - for_entity: Option>, - from: Option>, - to: Option>, - order: Option, - namespace: Option, - after: Option, - before: Option, - first: Option, - last: Option, + ctx: &Context<'a>, + activity_types: Option>, + for_agent: Option>, + for_entity: Option>, + from: Option>, + to: Option>, + order: Option, + namespace: Option, + after: Option, + before: Option, + first: Option, + last: Option, ) -> async_graphql::Result> { - use crate::persistence::schema::{ - activity, agent, association, delegation, entity, namespace::dsl as nsdsl, usage, - wasinformedby, - }; - - let store = ctx.data_unchecked::(); - - let mut connection = store.pool.get()?; - let ns = namespace.unwrap_or_else(|| "default".into()); - - // Default from and to to the maximum possible time range - let from = from.or_else(|| { - Some( - Utc.from_utc_datetime( - &NaiveDate::from_ymd_opt(1582, 10, 16) - .expect("Invalid date") - .and_hms_opt(0, 0, 0) - .expect("Invalid time"), - ), - ) - }); - - let to = to.or_else(|| Some(Utc::now())); - - let mut sql_query = activity::table - .left_join(wasinformedby::table.on(wasinformedby::activity_id.eq(activity::id))) - .left_join(usage::table.on(usage::activity_id.eq(activity::id))) - .left_join(generation::table.on(generation::activity_id.eq(activity::id))) - .left_join(association::table.on(association::activity_id.eq(activity::id))) - .left_join( - delegation::table.on(delegation::activity_id - .nullable() - .eq(activity::id.nullable())), - ) - .left_join( - entity::table.on(entity::id - .eq(usage::entity_id) - .or(entity::id.eq(generation::generated_entity_id))), - ) - .left_join( - agent::table.on(agent::id - .eq(association::agent_id) - .or(agent::id.eq(delegation::delegate_id)) - .or(agent::id.eq(delegation::responsible_id))), - ) - .inner_join(nsdsl::namespace.on(activity::namespace_id.eq(nsdsl::id))) - .filter(nsdsl::external_id.eq(&**ns)) - .filter(activity::started.ge(from.map(|x| x.naive_utc()))) - .filter(activity::ended.le(to.map(|x| x.naive_utc()))) - .distinct() - .select(Activity::as_select()) - .into_boxed(); - - if let Some(for_entity) = for_entity { - if !for_entity.is_empty() { - sql_query = sql_query.filter( - entity::external_id.eq_any( - for_entity - .iter() - .map(|x| x.external_id_part().clone()) - .collect::>(), - ), - ) - } - } - - if let Some(for_agent) = for_agent { - if !for_agent.is_empty() { - sql_query = sql_query.filter( - agent::external_id.eq_any( - for_agent - .iter() - .map(|x| x.external_id_part().clone()) - .collect::>(), - ), - ) - } - } - - if let Some(activity_types) = activity_types { - if !activity_types.is_empty() { - sql_query = sql_query.filter( - activity::domaintype.eq_any( - activity_types - .iter() - .map(|x| x.external_id_part().clone()) - .collect::>(), - ), - ); - } - } - - if order.unwrap_or(TimelineOrder::NewestFirst) == TimelineOrder::NewestFirst { - sql_query = sql_query.order_by(activity::started.desc()); - } else { - sql_query = sql_query.order_by(activity::started.asc()); - }; - - query( - after, - before, - first, - last, - |after, before, first, last| async move { - debug!( - "Cursor query {}", - debug_query::(&sql_query).to_string() - ); - let rx = sql_query.cursor(after, before, first, last); - - let start = rx.start; - let limit = rx.limit; - - let rx = rx.load::<(Activity, i64)>(&mut connection)?; - - Ok::<_, GraphQlError>(project_to_nodes(rx, start, limit)) - }, - ) - .await + use crate::persistence::schema::{ + activity, agent, association, delegation, entity, namespace::dsl as nsdsl, usage, + wasinformedby, + }; + + let store = ctx.data_unchecked::(); + + let mut connection = store.pool.get()?; + let ns = namespace.unwrap_or_else(|| "default".into()); + + // Default from and to to the maximum possible time range + let from = from.or_else(|| { + Some( + Utc.from_utc_datetime( + &NaiveDate::from_ymd_opt(1582, 10, 16) + .expect("Invalid date") + .and_hms_opt(0, 0, 0) + .expect("Invalid time"), + ), + ) + }); + + let to = to.or_else(|| Some(Utc::now())); + + let mut sql_query = activity::table + .left_join(wasinformedby::table.on(wasinformedby::activity_id.eq(activity::id))) + .left_join(usage::table.on(usage::activity_id.eq(activity::id))) + .left_join(generation::table.on(generation::activity_id.eq(activity::id))) + .left_join(association::table.on(association::activity_id.eq(activity::id))) + .left_join( + delegation::table.on(delegation::activity_id.nullable().eq(activity::id.nullable())), + ) + .left_join( + entity::table.on(entity::id + .eq(usage::entity_id) + .or(entity::id.eq(generation::generated_entity_id))), + ) + .left_join( + agent::table.on(agent::id + .eq(association::agent_id) + .or(agent::id.eq(delegation::delegate_id)) + .or(agent::id.eq(delegation::responsible_id))), + ) + .inner_join(nsdsl::namespace.on(activity::namespace_id.eq(nsdsl::id))) + .filter(nsdsl::external_id.eq(&**ns)) + .filter(activity::started.ge(from.map(|x| x.naive_utc()))) + .filter(activity::ended.le(to.map(|x| x.naive_utc()))) + .distinct() + .select(Activity::as_select()) + .into_boxed(); + + if let Some(for_entity) = for_entity { + if !for_entity.is_empty() { + sql_query = sql_query.filter(entity::external_id.eq_any( + for_entity.iter().map(|x| x.external_id_part().clone()).collect::>(), + )) + } + } + + if let Some(for_agent) = for_agent { + if !for_agent.is_empty() { + sql_query = + sql_query.filter(agent::external_id.eq_any( + for_agent.iter().map(|x| x.external_id_part().clone()).collect::>(), + )) + } + } + + if let Some(activity_types) = activity_types { + if !activity_types.is_empty() { + sql_query = sql_query.filter(activity::domaintype.eq_any( + activity_types.iter().map(|x| x.external_id_part().clone()).collect::>(), + )); + } + } + + if order.unwrap_or(TimelineOrder::NewestFirst) == TimelineOrder::NewestFirst { + sql_query = sql_query.order_by(activity::started.desc()); + } else { + sql_query = sql_query.order_by(activity::started.asc()); + }; + + query(after, before, first, last, |after, before, first, last| async move { + debug!("Cursor query {}", debug_query::(&sql_query).to_string()); + let rx = sql_query.cursor(after, before, first, last); + + let start = rx.start; + let limit = rx.limit; + + let rx = rx.load::<(Activity, i64)>(&mut connection)?; + + Ok::<_, GraphQlError>(project_to_nodes(rx, start, limit)) + }) + .await } #[allow(clippy::too_many_arguments)] pub async fn entities_by_type<'a>( - ctx: &Context<'a>, - typ: Option, - namespace: Option, - after: Option, - before: Option, - first: Option, - last: Option, + ctx: &Context<'a>, + typ: Option, + namespace: Option, + after: Option, + before: Option, + first: Option, + last: Option, ) -> async_graphql::Result> { - use crate::persistence::schema::{entity, namespace::dsl as nsdsl}; - - let store = ctx.data_unchecked::(); - - let mut connection = store.pool.get()?; - let ns = namespace.unwrap_or_else(|| "default".into()); - - let sql_query = entity::table - .inner_join(nsdsl::namespace) - .filter( - nsdsl::external_id - .eq(&**ns) - .and(entity::domaintype.eq(typ.as_ref().map(|x| x.external_id_part().to_owned()))), - ) - .select(Entity::as_select()) - .order_by(entity::external_id.asc()); - - query( - after, - before, - first, - last, - |after, before, first, last| async move { - debug!( - "Cursor query {}", - debug_query::(&sql_query).to_string() - ); - let rx = sql_query.cursor(after, before, first, last); - - let start = rx.start; - let limit = rx.limit; - - let rx = rx.load::<(Entity, i64)>(&mut connection)?; - - Ok::<_, GraphQlError>(project_to_nodes(rx, start, limit)) - }, - ) - .await + use crate::persistence::schema::{entity, namespace::dsl as nsdsl}; + + let store = ctx.data_unchecked::(); + + let mut connection = store.pool.get()?; + let ns = namespace.unwrap_or_else(|| "default".into()); + + let sql_query = entity::table + .inner_join(nsdsl::namespace) + .filter( + nsdsl::external_id + .eq(&**ns) + .and(entity::domaintype.eq(typ.as_ref().map(|x| x.external_id_part().to_owned()))), + ) + .select(Entity::as_select()) + .order_by(entity::external_id.asc()); + + query(after, before, first, last, |after, before, first, last| async move { + debug!("Cursor query {}", debug_query::(&sql_query).to_string()); + let rx = sql_query.cursor(after, before, first, last); + + let start = rx.start; + let limit = rx.limit; + + let rx = rx.load::<(Entity, i64)>(&mut connection)?; + + Ok::<_, GraphQlError>(project_to_nodes(rx, start, limit)) + }) + .await } #[allow(clippy::too_many_arguments)] pub async fn activities_by_type<'a>( - ctx: &Context<'a>, - typ: Option, - namespace: Option, - after: Option, - before: Option, - first: Option, - last: Option, + ctx: &Context<'a>, + typ: Option, + namespace: Option, + after: Option, + before: Option, + first: Option, + last: Option, ) -> async_graphql::Result> { - use crate::persistence::schema::{activity, namespace::dsl as nsdsl}; - - let store = ctx.data_unchecked::(); - - let mut connection = store.pool.get()?; - let ns = namespace.unwrap_or_else(|| "default".into()); - - let sql_query = - activity::table - .inner_join(nsdsl::namespace) - .filter(nsdsl::external_id.eq(&**ns).and( - activity::domaintype.eq(typ.as_ref().map(|x| x.external_id_part().to_owned())), - )) - .select(Activity::as_select()) - .order_by(activity::external_id.asc()); - - query( - after, - before, - first, - last, - |after, before, first, last| async move { - debug!( - "Cursor query {}", - debug_query::(&sql_query).to_string() - ); - let rx = sql_query.cursor(after, before, first, last); - - let start = rx.start; - let limit = rx.limit; - - let rx = rx.load::<(Activity, i64)>(&mut connection)?; - - Ok::<_, GraphQlError>(project_to_nodes(rx, start, limit)) - }, - ) - .await + use crate::persistence::schema::{activity, namespace::dsl as nsdsl}; + + let store = ctx.data_unchecked::(); + + let mut connection = store.pool.get()?; + let ns = namespace.unwrap_or_else(|| "default".into()); + + let sql_query = + activity::table + .inner_join(nsdsl::namespace) + .filter(nsdsl::external_id.eq(&**ns).and( + activity::domaintype.eq(typ.as_ref().map(|x| x.external_id_part().to_owned())), + )) + .select(Activity::as_select()) + .order_by(activity::external_id.asc()); + + query(after, before, first, last, |after, before, first, last| async move { + debug!("Cursor query {}", debug_query::(&sql_query).to_string()); + let rx = sql_query.cursor(after, before, first, last); + + let start = rx.start; + let limit = rx.limit; + + let rx = rx.load::<(Activity, i64)>(&mut connection)?; + + Ok::<_, GraphQlError>(project_to_nodes(rx, start, limit)) + }) + .await } #[allow(clippy::too_many_arguments)] pub async fn agents_by_type<'a>( - ctx: &Context<'a>, - typ: Option, - namespace: Option, - after: Option, - before: Option, - first: Option, - last: Option, + ctx: &Context<'a>, + typ: Option, + namespace: Option, + after: Option, + before: Option, + first: Option, + last: Option, ) -> async_graphql::Result> { - use crate::persistence::schema::{agent, namespace::dsl as nsdsl}; - - let store = ctx.data_unchecked::(); - - let mut connection = store.pool.get()?; - let ns = namespace.unwrap_or_else(|| "default".into()); - - let sql_query = agent::table - .inner_join(nsdsl::namespace) - .filter( - nsdsl::external_id - .eq(&**ns) - .and(agent::domaintype.eq(typ.as_ref().map(|x| x.external_id_part().to_owned()))), - ) - .select(Agent::as_select()) - .order_by(agent::external_id.asc()); - - query( - after, - before, - first, - last, - |after, before, first, last| async move { - debug!( - "Cursor query {}", - debug_query::(&sql_query).to_string() - ); - let rx = sql_query.cursor(after, before, first, last); - - let start = rx.start; - let limit = rx.limit; - - let rx = rx.load::<(Agent, i64)>(&mut connection)?; - - Ok::<_, GraphQlError>(project_to_nodes(rx, start, limit)) - }, - ) - .await + use crate::persistence::schema::{agent, namespace::dsl as nsdsl}; + + let store = ctx.data_unchecked::(); + + let mut connection = store.pool.get()?; + let ns = namespace.unwrap_or_else(|| "default".into()); + + let sql_query = agent::table + .inner_join(nsdsl::namespace) + .filter( + nsdsl::external_id + .eq(&**ns) + .and(agent::domaintype.eq(typ.as_ref().map(|x| x.external_id_part().to_owned()))), + ) + .select(Agent::as_select()) + .order_by(agent::external_id.asc()); + + query(after, before, first, last, |after, before, first, last| async move { + debug!("Cursor query {}", debug_query::(&sql_query).to_string()); + let rx = sql_query.cursor(after, before, first, last); + + let start = rx.start; + let limit = rx.limit; + + let rx = rx.load::<(Agent, i64)>(&mut connection)?; + + Ok::<_, GraphQlError>(project_to_nodes(rx, start, limit)) + }) + .await } pub async fn agent_by_id<'a>( - ctx: &Context<'a>, - id: AgentId, - namespace: Option, + ctx: &Context<'a>, + id: AgentId, + namespace: Option, ) -> async_graphql::Result> { - use crate::persistence::schema::{ - agent::{self, dsl}, - namespace::dsl as nsdsl, - }; - - let store = ctx.data_unchecked::(); - - let ns = namespace.unwrap_or_else(|| "default".into()); - let mut connection = store.pool.get()?; - - Ok(agent::table - .inner_join(nsdsl::namespace) - .filter( - dsl::external_id - .eq(id.external_id_part()) - .and(nsdsl::external_id.eq(&ns)), - ) - .select(Agent::as_select()) - .first::(&mut connection) - .optional()?) + use crate::persistence::schema::{ + agent::{self, dsl}, + namespace::dsl as nsdsl, + }; + + let store = ctx.data_unchecked::(); + + let ns = namespace.unwrap_or_else(|| "default".into()); + let mut connection = store.pool.get()?; + + Ok(agent::table + .inner_join(nsdsl::namespace) + .filter(dsl::external_id.eq(id.external_id_part()).and(nsdsl::external_id.eq(&ns))) + .select(Agent::as_select()) + .first::(&mut connection) + .optional()?) } pub async fn activity_by_id<'a>( - ctx: &Context<'a>, - id: ActivityId, - namespace: Option, + ctx: &Context<'a>, + id: ActivityId, + namespace: Option, ) -> async_graphql::Result> { - use crate::persistence::schema::{ - activity::{self, dsl}, - namespace::dsl as nsdsl, - }; - - let store = ctx.data_unchecked::(); - - let ns = namespace.unwrap_or_else(|| "default".into()); - let mut connection = store.pool.get()?; - - Ok(activity::table - .inner_join(nsdsl::namespace) - .filter( - dsl::external_id - .eq(id.external_id_part()) - .and(nsdsl::external_id.eq(&ns)), - ) - .select(Activity::as_select()) - .first::(&mut connection) - .optional()?) + use crate::persistence::schema::{ + activity::{self, dsl}, + namespace::dsl as nsdsl, + }; + + let store = ctx.data_unchecked::(); + + let ns = namespace.unwrap_or_else(|| "default".into()); + let mut connection = store.pool.get()?; + + Ok(activity::table + .inner_join(nsdsl::namespace) + .filter(dsl::external_id.eq(id.external_id_part()).and(nsdsl::external_id.eq(&ns))) + .select(Activity::as_select()) + .first::(&mut connection) + .optional()?) } pub async fn entity_by_id<'a>( - ctx: &Context<'a>, - id: EntityId, - namespace: Option, + ctx: &Context<'a>, + id: EntityId, + namespace: Option, ) -> async_graphql::Result> { - use crate::persistence::schema::{ - entity::{self, dsl}, - namespace::dsl as nsdsl, - }; - - let store = ctx.data_unchecked::(); - let ns = namespace.unwrap_or_else(|| "default".into()); - let mut connection = store.pool.get()?; - - Ok(entity::table - .inner_join(nsdsl::namespace) - .filter( - dsl::external_id - .eq(id.external_id_part()) - .and(nsdsl::external_id.eq(&ns)), - ) - .select(Entity::as_select()) - .first::(&mut connection) - .optional()?) + use crate::persistence::schema::{ + entity::{self, dsl}, + namespace::dsl as nsdsl, + }; + + let store = ctx.data_unchecked::(); + let ns = namespace.unwrap_or_else(|| "default".into()); + let mut connection = store.pool.get()?; + + Ok(entity::table + .inner_join(nsdsl::namespace) + .filter(dsl::external_id.eq(id.external_id_part()).and(nsdsl::external_id.eq(&ns))) + .select(Entity::as_select()) + .first::(&mut connection) + .optional()?) } diff --git a/crates/api/src/commands.rs b/crates/api/src/commands.rs new file mode 100644 index 000000000..3719d38fc --- /dev/null +++ b/crates/api/src/commands.rs @@ -0,0 +1,315 @@ +use core::pin::Pin; +#[cfg(feature = "std")] +use std::{path::PathBuf, sync::Arc}; + +use chrono::{DateTime, Utc}; +use futures::AsyncRead; + +use serde::{Deserialize, Serialize}; + +use crate::{ + attributes::Attributes, + prov::{ + operations::{ChronicleOperation, DerivationType}, + ActivityId, AgentId, ChronicleIri, ChronicleTransactionId, EntityId, ExternalId, + NamespaceId, ProvModel, Role, + }, +}; + +#[cfg(not(feature = "std"))] +use parity_scale_codec::{ + alloc::boxed::Box, alloc::string::String, alloc::sync::Arc, alloc::vec::Vec, +}; +#[cfg(not(feature = "std"))] +use scale_info::prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum NamespaceCommand { + Create { external_id: ExternalId }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum AgentCommand { + Create { + external_id: ExternalId, + namespace: ExternalId, + attributes: Attributes, + }, + UseInContext { + id: AgentId, + namespace: ExternalId, + }, + Delegate { + id: AgentId, + delegate: AgentId, + activity: Option, + namespace: ExternalId, + role: Option, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ActivityCommand { + Create { + external_id: ExternalId, + namespace: ExternalId, + attributes: Attributes, + }, + Instant { + id: ActivityId, + namespace: ExternalId, + time: Option>, + agent: Option, + }, + Start { + id: ActivityId, + namespace: ExternalId, + time: Option>, + agent: Option, + }, + End { + id: ActivityId, + namespace: ExternalId, + time: Option>, + agent: Option, + }, + Use { + id: EntityId, + namespace: ExternalId, + activity: ActivityId, + }, + Generate { + id: EntityId, + namespace: ExternalId, + activity: ActivityId, + }, + WasInformedBy { + id: ActivityId, + namespace: ExternalId, + informing_activity: ActivityId, + }, + Associate { + id: ActivityId, + namespace: ExternalId, + responsible: AgentId, + role: Option, + }, +} + +impl ActivityCommand { + pub fn create( + external_id: impl AsRef, + namespace: impl AsRef, + attributes: Attributes, + ) -> Self { + Self::Create { + external_id: external_id.as_ref().into(), + namespace: namespace.as_ref().into(), + attributes, + } + } + + pub fn start( + id: ActivityId, + namespace: impl AsRef, + time: Option>, + agent: Option, + ) -> Self { + Self::Start { id, namespace: namespace.as_ref().into(), time, agent } + } + + pub fn end( + id: ActivityId, + namespace: impl AsRef, + time: Option>, + agent: Option, + ) -> Self { + Self::End { id, namespace: namespace.as_ref().into(), time, agent } + } + + pub fn instant( + id: ActivityId, + namespace: impl AsRef, + time: Option>, + agent: Option, + ) -> Self { + Self::End { id, namespace: namespace.as_ref().into(), time, agent } + } + + pub fn r#use(id: EntityId, namespace: impl AsRef, activity: ActivityId) -> Self { + Self::Use { id, namespace: namespace.as_ref().into(), activity } + } + + pub fn was_informed_by( + id: ActivityId, + namespace: impl AsRef, + informing_activity: ActivityId, + ) -> Self { + Self::WasInformedBy { id, namespace: namespace.as_ref().into(), informing_activity } + } + + pub fn generate(id: EntityId, namespace: impl AsRef, activity: ActivityId) -> Self { + Self::Generate { id, namespace: namespace.as_ref().into(), activity } + } +} + +#[derive(Clone)] +pub enum PathOrFile { + Path(PathBuf), + File(Arc>>), /* Non serialisable variant, used in + * process */ +} + +impl core::fmt::Debug for PathOrFile { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + PathOrFile::Path(path) => f.debug_struct("Path").field("path", path).finish(), + PathOrFile::File(_) => f + .debug_struct("File") + .field("file", &"Non serialisable variant, used in process") + .finish(), + } + } +} + +impl Serialize for PathOrFile { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + PathOrFile::Path(path) => path.serialize(serializer), + _ => { + unreachable!() + }, + } + } +} + +impl<'de> Deserialize<'de> for PathOrFile { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(PathOrFile::Path(PathBuf::deserialize(deserializer)?)) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum EntityCommand { + Create { + external_id: ExternalId, + namespace: ExternalId, + attributes: Attributes, + }, + Attribute { + id: EntityId, + namespace: ExternalId, + responsible: AgentId, + role: Option, + }, + Derive { + id: EntityId, + namespace: ExternalId, + derivation: DerivationType, + activity: Option, + used_entity: EntityId, + }, +} + +impl EntityCommand { + pub fn create( + external_id: impl AsRef, + namespace: impl AsRef, + attributes: Attributes, + ) -> Self { + Self::Create { + external_id: external_id.as_ref().into(), + namespace: namespace.as_ref().into(), + attributes, + } + } + + pub fn detach( + id: EntityId, + namespace: impl AsRef, + derivation: DerivationType, + activity: Option, + used_entity: EntityId, + ) -> Self { + Self::Derive { id, namespace: namespace.as_ref().into(), derivation, activity, used_entity } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QueryCommand { + pub namespace: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DepthChargeCommand { + pub namespace: NamespaceId, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ImportCommand { + pub namespace: NamespaceId, + pub operations: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ApiCommand { + NameSpace(NamespaceCommand), + Agent(AgentCommand), + Activity(ActivityCommand), + Entity(EntityCommand), + Query(QueryCommand), + DepthCharge(DepthChargeCommand), + Import(ImportCommand), +} + +#[derive(Debug)] +pub enum ApiResponse { + /// The api has successfully executed the operation, but has no useful output + Unit, + /// The operation will not result in any data changes + AlreadyRecorded { subject: ChronicleIri, prov: Box }, + /// The api has validated the command and submitted a transaction to a ledger + Submission { subject: ChronicleIri, prov: Box, tx_id: ChronicleTransactionId }, + /// The api has successfully executed the query + QueryReply { prov: Box }, + /// The api has submitted the import transactions to a ledger + ImportSubmitted { prov: Box, tx_id: ChronicleTransactionId }, + /// The api has submitted the depth charge transaction to a ledger + DepthChargeSubmitted { tx_id: ChronicleTransactionId }, +} + +impl ApiResponse { + pub fn submission( + subject: impl Into, + prov: ProvModel, + tx_id: ChronicleTransactionId, + ) -> Self { + ApiResponse::Submission { subject: subject.into(), prov: Box::new(prov), tx_id } + } + + pub fn unit() -> Self { + ApiResponse::Unit + } + + pub fn query_reply(prov: ProvModel) -> Self { + ApiResponse::QueryReply { prov: Box::new(prov) } + } + + pub fn already_recorded(subject: impl Into, prov: ProvModel) -> Self { + ApiResponse::AlreadyRecorded { subject: subject.into(), prov: Box::new(prov) } + } + + pub fn depth_charge_submission(tx_id: ChronicleTransactionId) -> Self { + ApiResponse::DepthChargeSubmitted { tx_id } + } + + pub fn import_submitted(prov: ProvModel, tx_id: ChronicleTransactionId) -> Self { + ApiResponse::ImportSubmitted { prov: Box::new(prov), tx_id } + } +} diff --git a/crates/api/src/import.rs b/crates/api/src/import.rs new file mode 100644 index 000000000..92a2185c2 --- /dev/null +++ b/crates/api/src/import.rs @@ -0,0 +1,60 @@ +use std::{ + fs::File, + io::{self, Read}, + path::PathBuf, +}; + +use thiserror::Error; +use url::Url; + +#[derive(Error, Debug)] +pub enum FromUrlError { + #[error("HTTP error while attempting to read from URL: {0}")] + HTTP(#[from] reqwest::Error), + + #[error("Invalid URL scheme: {0}")] + InvalidUrlScheme(String), + + #[error("IO error while attempting to read from URL: {0}")] + IO(#[from] std::io::Error), +} + +pub enum PathOrUrl { + File(PathBuf), + Url(Url), +} + +pub async fn load_bytes_from_url(url: &str) -> Result, FromUrlError> { + let path_or_url = match url.parse::() { + Ok(url) => PathOrUrl::Url(url), + Err(_) => PathOrUrl::File(PathBuf::from(url)), + }; + + let content = match path_or_url { + PathOrUrl::File(path) => { + let mut file = File::open(path)?; + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + Ok(buf) + }, + PathOrUrl::Url(url) => match url.scheme() { + "file" => { + let mut file = File::open(url.path())?; + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + Ok(buf) + }, + "http" | "https" => Ok(reqwest::get(url).await?.bytes().await?.into()), + _ => Err(FromUrlError::InvalidUrlScheme(url.scheme().to_owned())), + }, + }?; + + Ok(content) +} + +pub fn load_bytes_from_stdin() -> Result, io::Error> { + let mut buffer = Vec::new(); + let mut stdin = io::stdin(); + let _ = stdin.read_to_end(&mut buffer)?; + Ok(buffer) +} diff --git a/crates/api/src/inmem.rs b/crates/api/src/inmem.rs index 64caaf829..73d0b8aa4 100644 --- a/crates/api/src/inmem.rs +++ b/crates/api/src/inmem.rs @@ -1,7 +1,7 @@ use async_stl_client::{ - error::SawtoothCommunicationError, - ledger::SawtoothLedger, - zmq_client::{HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel}, + error::SawtoothCommunicationError, + ledger::SawtoothLedger, + zmq_client::{HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel}, }; use chronicle_protocol::address::{FAMILY, VERSION}; use protobuf::{self, Message, ProtobufEnum}; @@ -10,32 +10,32 @@ use chronicle_protocol::{messages::ChronicleSubmitTransaction, protocol::Chronic use chronicle_sawtooth_tp::tp::ChronicleTransactionHandler; use futures::{select, FutureExt, SinkExt, StreamExt}; use sawtooth_sdk::{ - messages::{ - block::{Block, BlockHeader}, - client_batch_submit::{ - ClientBatchSubmitRequest, ClientBatchSubmitResponse, ClientBatchSubmitResponse_Status, - }, - client_block::{ - ClientBlockGetByNumRequest, ClientBlockGetResponse, ClientBlockGetResponse_Status, - ClientBlockListResponse, ClientBlockListResponse_Status, - }, - client_event::{ClientEventsSubscribeResponse, ClientEventsSubscribeResponse_Status}, - client_state::{ - ClientStateGetRequest, ClientStateGetResponse, ClientStateGetResponse_Status, - }, - processor::TpProcessRequest, - transaction::TransactionHeader, - validator::Message_MessageType, - }, - processor::handler::{ContextError, TransactionContext, TransactionHandler}, + messages::{ + block::{Block, BlockHeader}, + client_batch_submit::{ + ClientBatchSubmitRequest, ClientBatchSubmitResponse, ClientBatchSubmitResponse_Status, + }, + client_block::{ + ClientBlockGetByNumRequest, ClientBlockGetResponse, ClientBlockGetResponse_Status, + ClientBlockListResponse, ClientBlockListResponse_Status, + }, + client_event::{ClientEventsSubscribeResponse, ClientEventsSubscribeResponse_Status}, + client_state::{ + ClientStateGetRequest, ClientStateGetResponse, ClientStateGetResponse_Status, + }, + processor::TpProcessRequest, + transaction::TransactionHeader, + validator::Message_MessageType, + }, + processor::handler::{ContextError, TransactionContext, TransactionHandler}, }; use serde_json::Value; use std::{ - cell::RefCell, - collections::BTreeMap, - net::{Ipv4Addr, SocketAddr}, - sync::{Arc, Mutex}, - thread::{self}, + cell::RefCell, + collections::BTreeMap, + net::{Ipv4Addr, SocketAddr}, + sync::{Arc, Mutex}, + thread::{self}, }; use tmq::{router, Context, Multipart}; use tokio::runtime; @@ -46,303 +46,268 @@ use uuid::Uuid; type TestTxEvents = Vec<(String, Vec<(String, String)>, Vec)>; pub trait SimulatedSawtoothBehavior { - fn handle_request( - &self, - message_type: Message_MessageType, - request: Vec, - ) -> Result<(Message_MessageType, Vec), SawtoothCommunicationError>; + fn handle_request( + &self, + message_type: Message_MessageType, + request: Vec, + ) -> Result<(Message_MessageType, Vec), SawtoothCommunicationError>; } pub type InMemLedger = SawtoothLedger< - ZmqRequestResponseSawtoothChannel, - ChronicleOperationEvent, - ChronicleSubmitTransaction, + ZmqRequestResponseSawtoothChannel, + ChronicleOperationEvent, + ChronicleSubmitTransaction, >; pub struct SimulatedTransactionContext { - pub state: RefCell>>, - pub events: RefCell, - tx: tokio::sync::mpsc::UnboundedSender)>>, + pub state: RefCell>>, + pub events: RefCell, + tx: tokio::sync::mpsc::UnboundedSender)>>, } impl SimulatedTransactionContext { - pub fn new( - tx: tokio::sync::mpsc::UnboundedSender)>>, - ) -> Self { - Self { - state: RefCell::new(BTreeMap::new()), - events: RefCell::new(vec![]), - tx, - } - } - - pub fn new_with_state( - tx: tokio::sync::mpsc::UnboundedSender)>>, - state: BTreeMap>, - ) -> Self { - Self { - state: state.into(), - events: RefCell::new(vec![]), - tx, - } - } - - pub fn readable_state(&self) -> Vec<(String, Value)> { - // Deal with the fact that policies are raw bytes, but meta data and - // keys are json - - self.state - .borrow() - .iter() - .map(|(k, v)| { - let as_string = String::from_utf8(v.clone()).unwrap(); - if serde_json::from_str::(&as_string).is_ok() { - (k.clone(), serde_json::from_str(&as_string).unwrap()) - } else { - (k.clone(), serde_json::to_value(v.clone()).unwrap()) - } - }) - .collect() - } + pub fn new( + tx: tokio::sync::mpsc::UnboundedSender)>>, + ) -> Self { + Self { state: RefCell::new(BTreeMap::new()), events: RefCell::new(vec![]), tx } + } + + pub fn new_with_state( + tx: tokio::sync::mpsc::UnboundedSender)>>, + state: BTreeMap>, + ) -> Self { + Self { state: state.into(), events: RefCell::new(vec![]), tx } + } + + pub fn readable_state(&self) -> Vec<(String, Value)> { + // Deal with the fact that policies are raw bytes, but meta data and + // keys are json + + self.state + .borrow() + .iter() + .map(|(k, v)| { + let as_string = String::from_utf8(v.clone()).unwrap(); + if serde_json::from_str::(&as_string).is_ok() { + (k.clone(), serde_json::from_str(&as_string).unwrap()) + } else { + (k.clone(), serde_json::to_value(v.clone()).unwrap()) + } + }) + .collect() + } } impl TransactionContext for SimulatedTransactionContext { - fn add_receipt_data( - self: &SimulatedTransactionContext, - _data: &[u8], - ) -> Result<(), ContextError> { - unimplemented!() - } - - #[instrument(skip(self))] - fn add_event( - self: &SimulatedTransactionContext, - event_type: String, - attributes: Vec<(String, String)>, - data: &[u8], - ) -> Result<(), ContextError> { - let stl_event = sawtooth_sdk::messages::events::Event { - event_type: event_type.clone(), - attributes: attributes - .iter() - .map(|(k, v)| sawtooth_sdk::messages::events::Event_Attribute { - key: k.clone(), - value: v.clone(), - ..Default::default() - }) - .collect(), - data: data.to_vec(), - ..Default::default() - }; - let list = sawtooth_sdk::messages::events::EventList { - events: vec![stl_event].into(), - ..Default::default() - }; - let stl_event = list.write_to_bytes().unwrap(); - - self.tx - .send(Some((Message_MessageType::CLIENT_EVENTS, stl_event))) - .unwrap(); - - self.events - .borrow_mut() - .push((event_type, attributes, data.to_vec())); - - Ok(()) - } - - fn delete_state_entries( - self: &SimulatedTransactionContext, - _addresses: &[std::string::String], - ) -> Result, ContextError> { - unimplemented!() - } - - fn get_state_entries( - &self, - addresses: &[String], - ) -> Result)>, ContextError> { - Ok(self - .state - .borrow() - .iter() - .filter(|(k, _)| addresses.contains(k)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect()) - } - - fn set_state_entries( - self: &SimulatedTransactionContext, - entries: Vec<(String, Vec)>, - ) -> std::result::Result<(), sawtooth_sdk::processor::handler::ContextError> { - for entry in entries { - self.state.borrow_mut().insert(entry.0, entry.1); - } - - Ok(()) - } + fn add_receipt_data( + self: &SimulatedTransactionContext, + _data: &[u8], + ) -> Result<(), ContextError> { + unimplemented!() + } + + #[instrument(skip(self))] + fn add_event( + self: &SimulatedTransactionContext, + event_type: String, + attributes: Vec<(String, String)>, + data: &[u8], + ) -> Result<(), ContextError> { + let stl_event = sawtooth_sdk::messages::events::Event { + event_type: event_type.clone(), + attributes: attributes + .iter() + .map(|(k, v)| sawtooth_sdk::messages::events::Event_Attribute { + key: k.clone(), + value: v.clone(), + ..Default::default() + }) + .collect(), + data: data.to_vec(), + ..Default::default() + }; + let list = sawtooth_sdk::messages::events::EventList { + events: vec![stl_event].into(), + ..Default::default() + }; + let stl_event = list.write_to_bytes().unwrap(); + + self.tx.send(Some((Message_MessageType::CLIENT_EVENTS, stl_event))).unwrap(); + + self.events.borrow_mut().push((event_type, attributes, data.to_vec())); + + Ok(()) + } + + fn delete_state_entries( + self: &SimulatedTransactionContext, + _addresses: &[std::string::String], + ) -> Result, ContextError> { + unimplemented!() + } + + fn get_state_entries( + &self, + addresses: &[String], + ) -> Result)>, ContextError> { + Ok(self + .state + .borrow() + .iter() + .filter(|(k, _)| addresses.contains(k)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect()) + } + + fn set_state_entries( + self: &SimulatedTransactionContext, + entries: Vec<(String, Vec)>, + ) -> std::result::Result<(), sawtooth_sdk::processor::handler::ContextError> { + for entry in entries { + self.state.borrow_mut().insert(entry.0, entry.1); + } + + Ok(()) + } } #[derive(Clone)] pub struct WellBehavedBehavior { - handler: Arc, - context: Arc>, + handler: Arc, + context: Arc>, } impl SimulatedSawtoothBehavior for WellBehavedBehavior { - #[instrument(skip(self, request))] - fn handle_request( - &self, - message_type: Message_MessageType, - request: Vec, - ) -> Result<(Message_MessageType, Vec), SawtoothCommunicationError> { - match message_type { - // Batch submit request, decode and apply the transactions - // in the batch - Message_MessageType::CLIENT_BATCH_SUBMIT_REQUEST => { - let mut req = ClientBatchSubmitRequest::parse_from_bytes(&request).unwrap(); - let batch = req.take_batches().into_iter().next().unwrap(); - - debug!(received_batch = ?batch, transactions = ?batch.transactions); - - // Convert transaction into TpProcessRequest - for tx in batch.transactions { - let req = TpProcessRequest { - payload: tx.get_payload().to_vec(), - header: Some(TransactionHeader::parse_from_bytes(tx.get_header()).unwrap()) - .into(), - signature: tx.get_header_signature().to_string(), - ..Default::default() - }; - - self.handler - .as_ref() - .apply(&req, &mut *self.context.lock().unwrap()) - .unwrap(); - } - let mut response = ClientBatchSubmitResponse::new(); - response.set_status(ClientBatchSubmitResponse_Status::OK); - let mut buf = vec![]; - response.write_to_vec(&mut buf).unwrap(); - Ok((Message_MessageType::CLIENT_BATCH_SUBMIT_RESPONSE, buf)) - } - Message_MessageType::CLIENT_BLOCK_GET_BY_NUM_REQUEST => { - let req = ClientBlockGetByNumRequest::parse_from_bytes(&request).unwrap(); - debug!(get_block=?req); - let mut response = ClientBlockGetResponse::new(); - let block_header = BlockHeader { - block_num: req.get_block_num(), - previous_block_id: hex::encode([0; 32]), - ..Default::default() - }; - let block_header_bytes = block_header.write_to_bytes().unwrap(); - response.set_block(Block { - header: block_header_bytes, - ..Default::default() - }); - response.set_status(ClientBlockGetResponse_Status::OK); - let mut buf = vec![]; - response.write_to_vec(&mut buf).unwrap(); - Ok((Message_MessageType::CLIENT_BLOCK_GET_RESPONSE, buf)) - } - // Always respond with a block height of one - Message_MessageType::CLIENT_BLOCK_LIST_REQUEST => { - let mut response = ClientBlockListResponse::new(); - let block_header = BlockHeader { - block_num: 1, - ..Default::default() - }; - let block_header_bytes = block_header.write_to_bytes().unwrap(); - response.set_blocks( - vec![Block { - header: block_header_bytes, - ..Default::default() - }] - .into(), - ); - response.set_status(ClientBlockListResponse_Status::OK); - let mut buf = vec![]; - response.write_to_vec(&mut buf).unwrap(); - Ok((Message_MessageType::CLIENT_BLOCK_LIST_RESPONSE, buf)) - } - // We can just return Ok here, no need to fake routing - Message_MessageType::CLIENT_EVENTS_SUBSCRIBE_REQUEST => { - let mut response = ClientEventsSubscribeResponse::new(); - response.set_status(ClientEventsSubscribeResponse_Status::OK); - let mut buf = vec![]; - response.write_to_vec(&mut buf).unwrap(); - Ok((Message_MessageType::CLIENT_EVENTS_SUBSCRIBE_RESPONSE, buf)) - } - Message_MessageType::CLIENT_STATE_GET_REQUEST => { - let mut request = ClientStateGetRequest::parse_from_bytes(&request).unwrap(); - let address = request.take_address(); - - let state = self - .context - .lock() - .unwrap() - .get_state_entries(&[address]) - .unwrap(); - - let mut response = ClientStateGetResponse { - status: ClientStateGetResponse_Status::OK, - ..Default::default() - }; - - if state.is_empty() { - response.set_status(ClientStateGetResponse_Status::NO_RESOURCE); - } else { - response.set_value(state[0].1.clone()); - } - - let mut buf = vec![]; - response.write_to_vec(&mut buf).unwrap(); - Ok((Message_MessageType::CLIENT_STATE_GET_RESPONSE, buf)) - } - _ => panic!("Unexpected message type {} received", message_type as i32), - } - } + #[instrument(skip(self, request))] + fn handle_request( + &self, + message_type: Message_MessageType, + request: Vec, + ) -> Result<(Message_MessageType, Vec), SawtoothCommunicationError> { + match message_type { + // Batch submit request, decode and apply the transactions + // in the batch + Message_MessageType::CLIENT_BATCH_SUBMIT_REQUEST => { + let mut req = ClientBatchSubmitRequest::parse_from_bytes(&request).unwrap(); + let batch = req.take_batches().into_iter().next().unwrap(); + + debug!(received_batch = ?batch, transactions = ?batch.transactions); + + // Convert transaction into TpProcessRequest + for tx in batch.transactions { + let req = TpProcessRequest { + payload: tx.get_payload().to_vec(), + header: Some(TransactionHeader::parse_from_bytes(tx.get_header()).unwrap()) + .into(), + signature: tx.get_header_signature().to_string(), + ..Default::default() + }; + + self.handler.as_ref().apply(&req, &mut *self.context.lock().unwrap()).unwrap(); + } + let mut response = ClientBatchSubmitResponse::new(); + response.set_status(ClientBatchSubmitResponse_Status::OK); + let mut buf = vec![]; + response.write_to_vec(&mut buf).unwrap(); + Ok((Message_MessageType::CLIENT_BATCH_SUBMIT_RESPONSE, buf)) + }, + Message_MessageType::CLIENT_BLOCK_GET_BY_NUM_REQUEST => { + let req = ClientBlockGetByNumRequest::parse_from_bytes(&request).unwrap(); + debug!(get_block=?req); + let mut response = ClientBlockGetResponse::new(); + let block_header = BlockHeader { + block_num: req.get_block_num(), + previous_block_id: hex::encode([0; 32]), + ..Default::default() + }; + let block_header_bytes = block_header.write_to_bytes().unwrap(); + response.set_block(Block { header: block_header_bytes, ..Default::default() }); + response.set_status(ClientBlockGetResponse_Status::OK); + let mut buf = vec![]; + response.write_to_vec(&mut buf).unwrap(); + Ok((Message_MessageType::CLIENT_BLOCK_GET_RESPONSE, buf)) + }, + // Always respond with a block height of one + Message_MessageType::CLIENT_BLOCK_LIST_REQUEST => { + let mut response = ClientBlockListResponse::new(); + let block_header = BlockHeader { block_num: 1, ..Default::default() }; + let block_header_bytes = block_header.write_to_bytes().unwrap(); + response.set_blocks( + vec![Block { header: block_header_bytes, ..Default::default() }].into(), + ); + response.set_status(ClientBlockListResponse_Status::OK); + let mut buf = vec![]; + response.write_to_vec(&mut buf).unwrap(); + Ok((Message_MessageType::CLIENT_BLOCK_LIST_RESPONSE, buf)) + }, + // We can just return Ok here, no need to fake routing + Message_MessageType::CLIENT_EVENTS_SUBSCRIBE_REQUEST => { + let mut response = ClientEventsSubscribeResponse::new(); + response.set_status(ClientEventsSubscribeResponse_Status::OK); + let mut buf = vec![]; + response.write_to_vec(&mut buf).unwrap(); + Ok((Message_MessageType::CLIENT_EVENTS_SUBSCRIBE_RESPONSE, buf)) + }, + Message_MessageType::CLIENT_STATE_GET_REQUEST => { + let mut request = ClientStateGetRequest::parse_from_bytes(&request).unwrap(); + let address = request.take_address(); + + let state = self.context.lock().unwrap().get_state_entries(&[address]).unwrap(); + + let mut response = ClientStateGetResponse { + status: ClientStateGetResponse_Status::OK, + ..Default::default() + }; + + if state.is_empty() { + response.set_status(ClientStateGetResponse_Status::NO_RESOURCE); + } else { + response.set_value(state[0].1.clone()); + } + + let mut buf = vec![]; + response.write_to_vec(&mut buf).unwrap(); + Ok((Message_MessageType::CLIENT_STATE_GET_RESPONSE, buf)) + }, + _ => panic!("Unexpected message type {} received", message_type as i32), + } + } } pub struct EmbeddedChronicleTp { - pub ledger: InMemLedger, - context: Arc>, + pub ledger: InMemLedger, + context: Arc>, } impl EmbeddedChronicleTp { - pub fn new_with_state( - state: BTreeMap>, - ) -> Result { - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - - let context = Arc::new(Mutex::new(SimulatedTransactionContext::new_with_state( - tx, state, - ))); - - let (policy, entrypoint) = ("allow_transactions", "allow_transactions.allowed_users"); - - let handler = Arc::new(ChronicleTransactionHandler::new(policy, entrypoint).unwrap()); - - let behavior = WellBehavedBehavior { - handler, - context: context.clone(), - }; - - let listen_port = portpicker::pick_unused_port().expect("No ports free"); - let listen_addr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), listen_port); - let connect_addr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), listen_port); - - let behavior_clone = behavior; - thread::spawn(move || { - let rt = runtime::Builder::new_current_thread() - .enable_io() - .enable_time() - .build() - .unwrap(); - let mut rx = UnboundedReceiverStream::new(rx); - let local = tokio::task::LocalSet::new(); - - let task = local.run_until(async move { + pub fn new_with_state( + state: BTreeMap>, + ) -> Result { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + + let context = Arc::new(Mutex::new(SimulatedTransactionContext::new_with_state(tx, state))); + + let (policy, entrypoint) = ("allow_transactions", "allow_transactions.allowed_users"); + + let handler = Arc::new(ChronicleTransactionHandler::new(policy, entrypoint).unwrap()); + + let behavior = WellBehavedBehavior { handler, context: context.clone() }; + + let listen_port = portpicker::pick_unused_port().expect("No ports free"); + let listen_addr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), listen_port); + let connect_addr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), listen_port); + + let behavior_clone = behavior; + thread::spawn(move || { + let rt = runtime::Builder::new_current_thread() + .enable_io() + .enable_time() + .build() + .unwrap(); + let mut rx = UnboundedReceiverStream::new(rx); + let local = tokio::task::LocalSet::new(); + + let task = local.run_until(async move { tokio::task::spawn_local(async move { let (mut router_tx, mut router_rx) = router(&Context::new()) .bind(&format!("tcp://{}", listen_addr)) @@ -413,28 +378,28 @@ impl EmbeddedChronicleTp { }) .await }); - rt.block_on(task).ok(); - }); - - Ok(Self { - ledger: InMemLedger::new( - ZmqRequestResponseSawtoothChannel::new( - &format!("test_{}", Uuid::new_v4()), - &[connect_addr], - HighestBlockValidatorSelector, - )?, - FAMILY, - VERSION, - ), - context, - }) - } - - pub fn new() -> Result { - Self::new_with_state(BTreeMap::new()) - } - - pub fn readable_state(&self) -> Vec<(String, Value)> { - self.context.lock().unwrap().readable_state() - } + rt.block_on(task).ok(); + }); + + Ok(Self { + ledger: InMemLedger::new( + ZmqRequestResponseSawtoothChannel::new( + &format!("test_{}", Uuid::new_v4()), + &[connect_addr], + HighestBlockValidatorSelector, + )?, + FAMILY, + VERSION, + ), + context, + }) + } + + pub fn new() -> Result { + Self::new_with_state(BTreeMap::new()) + } + + pub fn readable_state(&self) -> Vec<(String, Value)> { + self.context.lock().unwrap().readable_state() + } } diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 0e3aee9dc..c835fa9d6 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -4,13 +4,13 @@ pub mod inmem; mod persistence; use async_stl_client::{ - error::SawtoothCommunicationError, - ledger::{BlockId, BlockingLedgerWriter, FromBlock}, + error::SawtoothCommunicationError, + ledger::{BlockId, BlockingLedgerWriter, FromBlock}, }; use chronicle_protocol::{ - async_stl_client::ledger::{LedgerReader, LedgerWriter}, - messages::ChronicleSubmitTransaction, - protocol::ChronicleOperationEvent, + async_stl_client::ledger::{LedgerReader, LedgerWriter}, + messages::ChronicleSubmitTransaction, + protocol::ChronicleOperationEvent, }; use chronicle_signing::{ChronicleSigning, SecretError}; use chrono::{DateTime, Utc}; @@ -20,22 +20,22 @@ use diesel_migrations::MigrationHarness; use futures::{select, FutureExt, StreamExt}; use common::{ - attributes::Attributes, - commands::*, - identity::{AuthId, IdentityError}, - ledger::{Commit, SubmissionError, SubmissionStage, SubscriptionError}, - prov::{ - operations::{ - ActivityExists, ActivityUses, ActsOnBehalfOf, AgentExists, ChronicleOperation, - CreateNamespace, DerivationType, EndActivity, EntityDerive, EntityExists, - SetAttributes, StartActivity, WasAssociatedWith, WasAttributedTo, WasGeneratedBy, - WasInformedBy, - }, - to_json_ld::ToJson, - ActivityId, AgentId, ChronicleIri, ChronicleTransaction, ChronicleTransactionId, - Contradiction, EntityId, ExternalId, ExternalIdPart, NamespaceId, ProcessorError, - ProvModel, Role, UuidPart, SYSTEM_ID, SYSTEM_UUID, - }, + attributes::Attributes, + commands::*, + identity::{AuthId, IdentityError}, + ledger::{Commit, SubmissionError, SubmissionStage, SubscriptionError}, + prov::{ + operations::{ + ActivityExists, ActivityUses, ActsOnBehalfOf, AgentExists, ChronicleOperation, + CreateNamespace, DerivationType, EndActivity, EntityDerive, EntityExists, + SetAttributes, StartActivity, WasAssociatedWith, WasAttributedTo, WasGeneratedBy, + WasInformedBy, + }, + to_json_ld::ToJson, + ActivityId, AgentId, ChronicleIri, ChronicleTransaction, ChronicleTransactionId, + Contradiction, EntityId, ExternalId, ExternalIdPart, NamespaceId, ProcessorError, + ProvModel, Role, UuidPart, SYSTEM_ID, SYSTEM_UUID, + }, }; use metrics::histogram; @@ -44,16 +44,16 @@ pub use persistence::StoreError; use persistence::{Store, MIGRATIONS}; use r2d2::Pool; use std::{ - convert::Infallible, - marker::PhantomData, - net::AddrParseError, - sync::Arc, - time::{Duration, Instant}, + convert::Infallible, + marker::PhantomData, + net::AddrParseError, + sync::Arc, + time::{Duration, Instant}, }; use thiserror::Error; use tokio::{ - sync::mpsc::{self, error::SendError, Sender}, - task::JoinError, + sync::mpsc::{self, error::SendError, Sender}, + task::JoinError, }; use tracing::{debug, error, info, info_span, instrument, trace, warn, Instrument}; @@ -64,1924 +64,1841 @@ use uuid::Uuid; #[derive(Error, Debug)] pub enum ApiError { - #[error("Storage: {0:?}")] - Store(#[from] persistence::StoreError), + #[error("Storage: {0:?}")] + Store(#[from] persistence::StoreError), - #[error("Transaction failed: {0}")] - Transaction(#[from] diesel::result::Error), + #[error("Transaction failed: {0}")] + Transaction(#[from] diesel::result::Error), - #[error("Invalid IRI: {0}")] - Iri(#[from] iref::Error), + #[error("Invalid IRI: {0}")] + Iri(#[from] iref::Error), - #[error("JSON-LD processing: {0}")] - JsonLD(String), + #[error("JSON-LD processing: {0}")] + JsonLD(String), - #[error("Ledger error: {0}")] - Ledger(#[from] SubmissionError), + #[error("Ledger error: {0}")] + Ledger(#[from] SubmissionError), - #[error("Signing: {0}")] - Signing(#[from] SecretError), + #[error("Signing: {0}")] + Signing(#[from] SecretError), - #[error("No agent is currently in use, please call agent use or supply an agent in your call")] - NoCurrentAgent, + #[error("No agent is currently in use, please call agent use or supply an agent in your call")] + NoCurrentAgent, - #[error("Api shut down before reply")] - ApiShutdownRx, + #[error("Api shut down before reply")] + ApiShutdownRx, - #[error("Api shut down before send: {0}")] - ApiShutdownTx(#[from] SendError), + #[error("Api shut down before send: {0}")] + ApiShutdownTx(#[from] SendError), - #[error("Ledger shut down before send: {0}")] - LedgerShutdownTx(#[from] SendError), + #[error("Ledger shut down before send: {0}")] + LedgerShutdownTx(#[from] SendError), - #[error("Invalid socket address: {0}")] - AddressParse(#[from] AddrParseError), + #[error("Invalid socket address: {0}")] + AddressParse(#[from] AddrParseError), - #[error("Connection pool: {0}")] - ConnectionPool(#[from] r2d2::Error), + #[error("Connection pool: {0}")] + ConnectionPool(#[from] r2d2::Error), - #[error("IO error: {0}")] - InputOutput(#[from] std::io::Error), + #[error("IO error: {0}")] + InputOutput(#[from] std::io::Error), - #[error("Blocking thread pool: {0}")] - Join(#[from] JoinError), + #[error("Blocking thread pool: {0}")] + Join(#[from] JoinError), - #[error("State update subscription: {0}")] - Subscription(#[from] SubscriptionError), + #[error("State update subscription: {0}")] + Subscription(#[from] SubscriptionError), - #[error("No appropriate activity to end")] - NotCurrentActivity, + #[error("No appropriate activity to end")] + NotCurrentActivity, - #[error("Contradiction: {0}")] - Contradiction(#[from] Contradiction), + #[error("Contradiction: {0}")] + Contradiction(#[from] Contradiction), - #[error("Processor: {0}")] - ProcessorError(#[from] ProcessorError), + #[error("Processor: {0}")] + ProcessorError(#[from] ProcessorError), - #[error("Identity: {0}")] - IdentityError(#[from] IdentityError), + #[error("Identity: {0}")] + IdentityError(#[from] IdentityError), - #[error("Sawtooth communication error: {0}")] - SawtoothCommunicationError(#[from] SawtoothCommunicationError), + #[error("Sawtooth communication error: {0}")] + SawtoothCommunicationError(#[from] SawtoothCommunicationError), - #[error("Authentication endpoint error: {0}")] - AuthenticationEndpoint(#[from] chronicle_graphql::AuthorizationError), + #[error("Authentication endpoint error: {0}")] + AuthenticationEndpoint(#[from] chronicle_graphql::AuthorizationError), } /// Ugly but we need this until ! is stable, see impl From for ApiError { - fn from(_: Infallible) -> Self { - unreachable!() - } + fn from(_: Infallible) -> Self { + unreachable!() + } } impl UFE for ApiError {} -type LedgerSendWithReply = ( - ChronicleSubmitTransaction, - Sender>, -); +type LedgerSendWithReply = + (ChronicleSubmitTransaction, Sender>); type ApiSendWithReply = ((ApiCommand, AuthId), Sender>); pub trait UuidGen { - fn uuid() -> Uuid { - Uuid::new_v4() - } + fn uuid() -> Uuid { + Uuid::new_v4() + } +} + +pub trait ChronicleSigned { + /// Get the user identity's [`SignedIdentity`] + pub fn signed_identity( + &self, + store: &S, + ) -> Result; +} + +impl ChronicleSigned for Identity { + pub fn signed_identity( + &self, + store: &S, + ) -> Result { + let buf = serde_json::to_string(self)?.as_bytes().to_vec(); + + futures::executor::block_on(async move { + SignedIdentity::new( + self, + store.chronicle_sign(&buf).await?, + store.chronicle_verifying().await?, + ) + }) + } } #[derive(Clone)] pub struct Api< - U: UuidGen + Send + Sync + Clone, - W: LedgerWriter - + Clone - + Send - + Sync - + 'static, + U: UuidGen + Send + Sync + Clone, + W: LedgerWriter + + Clone + + Send + + Sync + + 'static, > { - _reply_tx: Sender, - submit_tx: tokio::sync::broadcast::Sender, - signing: ChronicleSigning, - ledger_writer: Arc>, - store: persistence::Store, - uuid_source: PhantomData, - policy_name: Option, + _reply_tx: Sender, + submit_tx: tokio::sync::broadcast::Sender, + signing: ChronicleSigning, + ledger_writer: Arc>, + store: persistence::Store, + uuid_source: PhantomData, + policy_name: Option, } #[derive(Debug, Clone)] /// A clonable api handle pub struct ApiDispatch { - tx: Sender, - pub notify_commit: tokio::sync::broadcast::Sender, + tx: Sender, + pub notify_commit: tokio::sync::broadcast::Sender, } impl ApiDispatch { - #[instrument] - pub async fn dispatch( - &self, - command: ApiCommand, - identity: AuthId, - ) -> Result { - let (reply_tx, mut reply_rx) = mpsc::channel(1); - trace!(?command, "Dispatch command to api"); - self.tx - .clone() - .send(((command, identity), reply_tx)) - .await?; - - let reply = reply_rx.recv().await; - - if let Some(Err(ref error)) = reply { - error!(?error, "Api dispatch"); - } - - reply.ok_or(ApiError::ApiShutdownRx {})? - } - - #[instrument] - pub async fn handle_import_command( - &self, - identity: AuthId, - namespace: NamespaceId, - operations: Vec, - ) -> Result { - self.import_operations(identity, namespace, operations) - .await - } - - #[instrument] - async fn import_operations( - &self, - identity: AuthId, - namespace: NamespaceId, - operations: Vec, - ) -> Result { - self.dispatch( - ApiCommand::Import(ImportCommand { - namespace, - operations, - }), - identity.clone(), - ) - .await - } - - #[instrument] - pub async fn handle_depth_charge( - &self, - namespace: &str, - uuid: &Uuid, - ) -> Result { - self.dispatch_depth_charge( - AuthId::Chronicle, - NamespaceId::from_external_id(namespace, *uuid), - ) - .await - } - - #[instrument] - async fn dispatch_depth_charge( - &self, - identity: AuthId, - namespace: NamespaceId, - ) -> Result { - self.dispatch( - ApiCommand::DepthCharge(DepthChargeCommand { namespace }), - identity.clone(), - ) - .await - } + #[instrument] + pub async fn dispatch( + &self, + command: ApiCommand, + identity: AuthId, + ) -> Result { + let (reply_tx, mut reply_rx) = mpsc::channel(1); + trace!(?command, "Dispatch command to api"); + self.tx.clone().send(((command, identity), reply_tx)).await?; + + let reply = reply_rx.recv().await; + + if let Some(Err(ref error)) = reply { + error!(?error, "Api dispatch"); + } + + reply.ok_or(ApiError::ApiShutdownRx {})? + } + + #[instrument] + pub async fn handle_import_command( + &self, + identity: AuthId, + namespace: NamespaceId, + operations: Vec, + ) -> Result { + self.import_operations(identity, namespace, operations).await + } + + #[instrument] + async fn import_operations( + &self, + identity: AuthId, + namespace: NamespaceId, + operations: Vec, + ) -> Result { + self.dispatch(ApiCommand::Import(ImportCommand { namespace, operations }), identity.clone()) + .await + } + + #[instrument] + pub async fn handle_depth_charge( + &self, + namespace: &str, + uuid: &Uuid, + ) -> Result { + self.dispatch_depth_charge( + AuthId::Chronicle, + NamespaceId::from_external_id(namespace, *uuid), + ) + .await + } + + #[instrument] + async fn dispatch_depth_charge( + &self, + identity: AuthId, + namespace: NamespaceId, + ) -> Result { + self.dispatch(ApiCommand::DepthCharge(DepthChargeCommand { namespace }), identity.clone()) + .await + } } fn install_prometheus_metrics_exporter() { - let metrics_endpoint = "127.0.0.1:9000"; - let metrics_listen_socket = match metrics_endpoint.parse::() { - Ok(addr) => addr, - Err(e) => { - error!("Unable to parse metrics listen socket address: {e:?}"); - return; - } - }; - - if let Err(e) = PrometheusBuilder::new() - .with_http_listener(metrics_listen_socket) - .install() - { - error!("Prometheus exporter installation for liveness check metrics failed: {e:?}"); - } else { - debug!("Liveness check metrics Prometheus exporter installed with endpoint on {metrics_endpoint}/metrics"); - } + let metrics_endpoint = "127.0.0.1:9000"; + let metrics_listen_socket = match metrics_endpoint.parse::() { + Ok(addr) => addr, + Err(e) => { + error!("Unable to parse metrics listen socket address: {e:?}"); + return; + }, + }; + + if let Err(e) = PrometheusBuilder::new().with_http_listener(metrics_listen_socket).install() { + error!("Prometheus exporter installation for liveness check metrics failed: {e:?}"); + } else { + debug!("Liveness check metrics Prometheus exporter installed with endpoint on {metrics_endpoint}/metrics"); + } } impl Api where - U: UuidGen + Send + Sync + Clone + std::fmt::Debug + 'static, - LEDGER: LedgerWriter - + Clone - + Send - + Sync - + 'static - + LedgerReader, + U: UuidGen + Send + Sync + Clone + core::fmt::Debug + 'static, + LEDGER: LedgerWriter + + Clone + + Send + + Sync + + 'static + + LedgerReader, { - #[instrument(skip(ledger))] - pub async fn new( - pool: Pool>, - ledger: LEDGER, - uuidgen: U, - signing: ChronicleSigning, - namespace_bindings: Vec, - policy_name: Option, - liveness_check_interval: Option, - ) -> Result { - let (commit_tx, mut commit_rx) = mpsc::channel::(10); - - let (commit_notify_tx, _) = tokio::sync::broadcast::channel(20); - let dispatch = ApiDispatch { - tx: commit_tx.clone(), - notify_commit: commit_notify_tx.clone(), - }; - - let store = Store::new(pool.clone())?; - - pool.get()? - .build_transaction() - .run(|connection| connection.run_pending_migrations(MIGRATIONS).map(|_| ())) - .map_err(StoreError::DbMigration)?; - - let system_namespace_uuid = (SYSTEM_ID, Uuid::try_from(SYSTEM_UUID).unwrap()); - - // Append namespace bindings and system namespace - store.namespace_binding(system_namespace_uuid.0, system_namespace_uuid.1)?; - for ns in namespace_bindings { - store.namespace_binding(ns.external_id_part().as_str(), ns.uuid_part().to_owned())? - } - - let reuse_reader = ledger.clone(); - - let last_seen_block = store.get_last_block_id(); - - let start_from_block = if let Ok(Some(start_from_block)) = last_seen_block { - FromBlock::BlockId(start_from_block) - } else { - FromBlock::First //Full catch up, as we have no last seen block - }; - - debug!(start_from_block = ?start_from_block, "Starting from block"); - - tokio::task::spawn(async move { - let mut api = Api:: { - _reply_tx: commit_tx.clone(), - submit_tx: commit_notify_tx.clone(), - signing, - ledger_writer: Arc::new(BlockingLedgerWriter::new(ledger)), - store: store.clone(), - uuid_source: PhantomData, - policy_name, - }; - - loop { - let state_updates = reuse_reader.clone(); - - let state_updates = state_updates - .state_updates("chronicle/prov-update", start_from_block, None) - .await; - - if let Err(e) = state_updates { - error!(subscribe_to_events = ?e); - tokio::time::sleep(Duration::from_secs(2)).await; - continue; - } - - let mut state_updates = state_updates.unwrap(); - - loop { - select! { - state = state_updates.next().fuse() =>{ - - match state { - None => { - debug!("Ledger reader stream ended"); - break; - } - // Ledger contradicted or error, so nothing to - // apply, but forward notification - Some((ChronicleOperationEvent(Err(e), id),tx,_block_id,_position, _span)) => { - commit_notify_tx.send(SubmissionStage::not_committed( - ChronicleTransactionId::from(tx.as_str()),e.clone(), id - )).ok(); - }, - // Successfully committed to ledger, so apply - // to db and broadcast notification to - // subscription subscribers - Some((ChronicleOperationEvent(Ok(ref commit), id,),tx,block_id,_position,_span )) => { - - debug!(committed = ?tx); - debug!(delta = %serde_json::to_string_pretty(&commit.to_json().compact().await.unwrap()).unwrap()); - - api.sync( commit.clone().into(), &block_id,ChronicleTransactionId::from(tx.as_str())) - .instrument(info_span!("Incoming confirmation", offset = ?block_id, tx_id = %tx)) - .await - .map_err(|e| { - error!(?e, "Api sync to confirmed commit"); - }).map(|_| commit_notify_tx.send(SubmissionStage::committed(Commit::new( - ChronicleTransactionId::from(tx.as_str()),block_id, Box::new(commit.clone()) - ), id )).ok()) - .ok(); - }, - } - }, - cmd = commit_rx.recv().fuse() => { - if let Some((command, reply)) = cmd { - - let result = api - .dispatch(command) - .await; - - reply - .send(result) - .await - .map_err(|e| { - warn!(?e, "Send reply to Api consumer failed"); - }) - .ok(); - } - } - complete => break - } - } - } - }); - - if let Some(interval) = liveness_check_interval { - debug!("Starting liveness depth charge task"); - - let depth_charge_api = dispatch.clone(); - - tokio::task::spawn(async move { - // Configure and install Prometheus exporter - install_prometheus_metrics_exporter(); - - loop { - tokio::time::sleep(std::time::Duration::from_secs(interval)).await; - let api = depth_charge_api.clone(); - - let start_time = Instant::now(); - - let response = api - .handle_depth_charge(system_namespace_uuid.0, &system_namespace_uuid.1) - .await; - - match response { - Ok(ApiResponse::DepthChargeSubmitted { tx_id }) => { - let mut tx_notifications = api.notify_commit.subscribe(); - - loop { - let stage = match tx_notifications.recv().await { - Ok(stage) => stage, - Err(e) => { - error!("Error receiving depth charge transaction notifications: {}", e); - continue; - } - }; - - match stage { - SubmissionStage::Submitted(Ok(id)) => { - if id == tx_id { - debug!("Depth charge operation submitted: {id}"); - continue; - } - } - SubmissionStage::Submitted(Err(err)) => { - if err.tx_id() == &tx_id { - error!("Depth charge transaction rejected by Chronicle: {} {}", + #[instrument(skip(ledger))] + pub async fn new( + pool: Pool>, + ledger: LEDGER, + uuidgen: U, + signing: ChronicleSigning, + namespace_bindings: Vec, + policy_name: Option, + liveness_check_interval: Option, + ) -> Result { + let (commit_tx, mut commit_rx) = mpsc::channel::(10); + + let (commit_notify_tx, _) = tokio::sync::broadcast::channel(20); + let dispatch = + ApiDispatch { tx: commit_tx.clone(), notify_commit: commit_notify_tx.clone() }; + + let store = Store::new(pool.clone())?; + + pool.get()? + .build_transaction() + .run(|connection| connection.run_pending_migrations(MIGRATIONS).map(|_| ())) + .map_err(StoreError::DbMigration)?; + + let system_namespace_uuid = (SYSTEM_ID, Uuid::try_from(SYSTEM_UUID).unwrap()); + + // Append namespace bindings and system namespace + store.namespace_binding(system_namespace_uuid.0, system_namespace_uuid.1)?; + for ns in namespace_bindings { + store.namespace_binding(ns.external_id_part().as_str(), ns.uuid_part().to_owned())? + } + + let reuse_reader = ledger.clone(); + + let last_seen_block = store.get_last_block_id(); + + let start_from_block = if let Ok(Some(start_from_block)) = last_seen_block { + FromBlock::BlockId(start_from_block) + } else { + FromBlock::First //Full catch up, as we have no last seen block + }; + + debug!(start_from_block = ?start_from_block, "Starting from block"); + + tokio::task::spawn(async move { + let mut api = Api:: { + _reply_tx: commit_tx.clone(), + submit_tx: commit_notify_tx.clone(), + signing, + ledger_writer: Arc::new(BlockingLedgerWriter::new(ledger)), + store: store.clone(), + uuid_source: PhantomData, + policy_name, + }; + + loop { + let state_updates = reuse_reader.clone(); + + let state_updates = state_updates + .state_updates("chronicle/prov-update", start_from_block, None) + .await; + + if let Err(e) = state_updates { + error!(subscribe_to_events = ?e); + tokio::time::sleep(Duration::from_secs(2)).await; + continue; + } + + let mut state_updates = state_updates.unwrap(); + + loop { + select! { + state = state_updates.next().fuse() =>{ + + match state { + None => { + debug!("Ledger reader stream ended"); + break; + } + // Ledger contradicted or error, so nothing to + // apply, but forward notification + Some((ChronicleOperationEvent(Err(e), id),tx,_block_id,_position, _span)) => { + commit_notify_tx.send(SubmissionStage::not_committed( + ChronicleTransactionId::from(tx.as_str()),e.clone(), id + )).ok(); + }, + // Successfully committed to ledger, so apply + // to db and broadcast notification to + // subscription subscribers + Some((ChronicleOperationEvent(Ok(ref commit), id,),tx,block_id,_position,_span )) => { + + debug!(committed = ?tx); + debug!(delta = %serde_json::to_string_pretty(&commit.to_json().compact().await.unwrap()).unwrap()); + + api.sync( commit.clone().into(), &block_id,ChronicleTransactionId::from(tx.as_str())) + .instrument(info_span!("Incoming confirmation", offset = ?block_id, tx_id = %tx)) + .await + .map_err(|e| { + error!(?e, "Api sync to confirmed commit"); + }).map(|_| commit_notify_tx.send(SubmissionStage::committed(Commit::new( + ChronicleTransactionId::from(tx.as_str()),block_id, Box::new(commit.clone()) + ), id )).ok()) + .ok(); + }, + } + }, + cmd = commit_rx.recv().fuse() => { + if let Some((command, reply)) = cmd { + + let result = api + .dispatch(command) + .await; + + reply + .send(result) + .await + .map_err(|e| { + warn!(?e, "Send reply to Api consumer failed"); + }) + .ok(); + } + } + complete => break + } + } + } + }); + + if let Some(interval) = liveness_check_interval { + debug!("Starting liveness depth charge task"); + + let depth_charge_api = dispatch.clone(); + + tokio::task::spawn(async move { + // Configure and install Prometheus exporter + install_prometheus_metrics_exporter(); + + loop { + tokio::time::sleep(std::time::Duration::from_secs(interval)).await; + let api = depth_charge_api.clone(); + + let start_time = Instant::now(); + + let response = api + .handle_depth_charge(system_namespace_uuid.0, &system_namespace_uuid.1) + .await; + + match response { + Ok(ApiResponse::DepthChargeSubmitted { tx_id }) => { + let mut tx_notifications = api.notify_commit.subscribe(); + + loop { + let stage = match tx_notifications.recv().await { + Ok(stage) => stage, + Err(e) => { + error!("Error receiving depth charge transaction notifications: {}", e); + continue; + }, + }; + + match stage { + SubmissionStage::Submitted(Ok(id)) => { + if id == tx_id { + debug!("Depth charge operation submitted: {id}"); + continue; + } + }, + SubmissionStage::Submitted(Err(err)) => { + if err.tx_id() == &tx_id { + error!("Depth charge transaction rejected by Chronicle: {} {}", err, err.tx_id() ); - break; - } - } - SubmissionStage::Committed(commit, _) => { - if commit.tx_id == tx_id { - let end_time = Instant::now(); - let elapsed_time = end_time - start_time; - - debug!( - "Depth charge transaction committed: {}", - commit.tx_id - ); - debug!( - "Depth charge round trip time: {:.2?}", - elapsed_time - ); - histogram!( - "depth_charge_round_trip", - elapsed_time.as_millis() as f64 - ); - break; - } - } - SubmissionStage::NotCommitted((id, contradiction, _)) => { - if id == tx_id { - error!("Depth charge transaction rejected by ledger: {id} {contradiction}"); - break; - } - } - } - } - } - Ok(res) => error!("Unexpected ApiResponse from depth charge: {res:?}"), - Err(e) => error!("ApiError submitting depth charge: {e}"), - } - } - }); - } - - Ok(dispatch) - } - - /// Notify after a successful submission, for now this makes little - /// difference, but with the future introduction of a submission queue, - /// submission notifications will be decoupled from api invocation. - /// This is a measure to keep the api interface stable once this is introduced - fn submit_blocking( - &mut self, - tx: &ChronicleTransaction, - ) -> Result { - let res = self.ledger_writer.submit(&ChronicleSubmitTransaction { - tx: tx.clone(), - signer: self.signing.clone(), - policy_name: self.policy_name.clone(), - }); - - match res { - Ok(tx_id) => { - let tx_id = ChronicleTransactionId::from(tx_id.as_str()); - self.submit_tx.send(SubmissionStage::submitted(&tx_id)).ok(); - Ok(tx_id) - } - Err((Some(tx_id), e)) => { - // We need the cloneable SubmissionError wrapper here - let submission_error = SubmissionError::communication( - &ChronicleTransactionId::from(tx_id.as_str()), - e, - ); - self.submit_tx - .send(SubmissionStage::submitted_error(&submission_error)) - .ok(); - Err(submission_error.into()) - } - Err((None, e)) => Err(e.into()), - } - } - - /// Generate and submit the signed identity to send to the Transaction Processor along with the transactions to be applied - fn submit( - &mut self, - id: impl Into, - identity: AuthId, - to_apply: Vec, - ) -> Result { - let identity = identity.signed_identity(&self.signing)?; - let model = ProvModel::from_tx(&to_apply)?; - let tx_id = self.submit_blocking(&ChronicleTransaction::new(to_apply, identity))?; - - Ok(ApiResponse::submission(id, model, tx_id)) - } - - /// Checks if ChronicleOperations resulting from Chronicle API calls will result in any changes in state - /// - /// # Arguments - /// * `connection` - Connection to the Chronicle database - /// * `to_apply` - Chronicle operations resulting from an API call - #[instrument(skip(self, connection))] - fn check_for_effects( - &mut self, - connection: &mut PgConnection, - to_apply: &Vec, - ) -> Result>, ApiError> { - let mut model = ProvModel::default(); - let mut transactions = Vec::::with_capacity(to_apply.len()); - for op in to_apply { - let mut applied_model = match op { - ChronicleOperation::CreateNamespace(CreateNamespace { external_id, .. }) => { - let (namespace, _) = self.ensure_namespace(connection, external_id)?; - model.namespace_context(&namespace); - model - } - ChronicleOperation::AgentExists(AgentExists { - ref namespace, - ref external_id, - }) => { - model.namespace_context(namespace); - self.store.apply_prov_model_for_agent_id( - connection, - model, - &AgentId::from_external_id(external_id), - namespace.external_id_part(), - )? - } - ChronicleOperation::ActivityExists(ActivityExists { - ref namespace, - ref external_id, - }) => { - model.namespace_context(namespace); - - self.store.apply_prov_model_for_activity_id( - connection, - model, - &ActivityId::from_external_id(external_id), - namespace.external_id_part(), - )? - } - ChronicleOperation::EntityExists(EntityExists { - ref namespace, - ref external_id, - }) => { - model.namespace_context(namespace); - self.store.apply_prov_model_for_entity_id( - connection, - model, - &EntityId::from_external_id(external_id), - namespace.external_id_part(), - )? - } - ChronicleOperation::ActivityUses(ActivityUses { - ref namespace, - ref id, - ref activity, - }) => { - model.namespace_context(namespace); - self.store.prov_model_for_usage( - connection, - model, - id, - activity, - namespace.external_id_part(), - )? - } - ChronicleOperation::SetAttributes(ref o) => match o { - SetAttributes::Activity { namespace, id, .. } => { - model.namespace_context(namespace); - self.store.apply_prov_model_for_activity_id( - connection, - model, - id, - namespace.external_id_part(), - )? - } - SetAttributes::Agent { namespace, id, .. } => { - model.namespace_context(namespace); - self.store.apply_prov_model_for_agent_id( - connection, - model, - id, - namespace.external_id_part(), - )? - } - SetAttributes::Entity { namespace, id, .. } => { - model.namespace_context(namespace); - self.store.apply_prov_model_for_entity_id( - connection, - model, - id, - namespace.external_id_part(), - )? - } - }, - ChronicleOperation::StartActivity(StartActivity { namespace, id, .. }) => { - model.namespace_context(namespace); - self.store.apply_prov_model_for_activity_id( - connection, - model, - id, - namespace.external_id_part(), - )? - } - ChronicleOperation::EndActivity(EndActivity { namespace, id, .. }) => { - model.namespace_context(namespace); - self.store.apply_prov_model_for_activity_id( - connection, - model, - id, - namespace.external_id_part(), - )? - } - ChronicleOperation::WasInformedBy(WasInformedBy { - namespace, - activity, - informing_activity, - }) => { - model.namespace_context(namespace); - let model = self.store.apply_prov_model_for_activity_id( - connection, - model, - activity, - namespace.external_id_part(), - )?; - self.store.apply_prov_model_for_activity_id( - connection, - model, - informing_activity, - namespace.external_id_part(), - )? - } - ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf { - activity_id, - responsible_id, - delegate_id, - namespace, - .. - }) => { - model.namespace_context(namespace); - let model = self.store.apply_prov_model_for_agent_id( - connection, - model, - responsible_id, - namespace.external_id_part(), - )?; - let model = self.store.apply_prov_model_for_agent_id( - connection, - model, - delegate_id, - namespace.external_id_part(), - )?; - if let Some(id) = activity_id { - self.store.apply_prov_model_for_activity_id( - connection, - model, - id, - namespace.external_id_part(), - )? - } else { - model - } - } - ChronicleOperation::WasAssociatedWith(WasAssociatedWith { - namespace, - activity_id, - agent_id, - .. - }) => { - model.namespace_context(namespace); - let model = self.store.apply_prov_model_for_activity_id( - connection, - model, - activity_id, - namespace.external_id_part(), - )?; - - self.store.apply_prov_model_for_agent_id( - connection, - model, - agent_id, - namespace.external_id_part(), - )? - } - ChronicleOperation::WasGeneratedBy(WasGeneratedBy { - namespace, - id, - activity, - }) => { - model.namespace_context(namespace); - let model = self.store.apply_prov_model_for_activity_id( - connection, - model, - activity, - namespace.external_id_part(), - )?; - - self.store.apply_prov_model_for_entity_id( - connection, - model, - id, - namespace.external_id_part(), - )? - } - ChronicleOperation::EntityDerive(EntityDerive { - namespace, - id, - used_id, - activity_id, - .. - }) => { - model.namespace_context(namespace); - let model = self.store.apply_prov_model_for_entity_id( - connection, - model, - id, - namespace.external_id_part(), - )?; - - let model = self.store.apply_prov_model_for_entity_id( - connection, - model, - used_id, - namespace.external_id_part(), - )?; - - if let Some(id) = activity_id { - self.store.apply_prov_model_for_activity_id( - connection, - model, - id, - namespace.external_id_part(), - )? - } else { - model - } - } - ChronicleOperation::WasAttributedTo(WasAttributedTo { - namespace, - entity_id, - agent_id, - .. - }) => { - model.namespace_context(namespace); - let model = self.store.apply_prov_model_for_entity_id( - connection, - model, - entity_id, - namespace.external_id_part(), - )?; - - self.store.apply_prov_model_for_agent_id( - connection, - model, - agent_id, - namespace.external_id_part(), - )? - } - }; - let state = applied_model.clone(); - applied_model.apply(op)?; - if state != applied_model { - transactions.push(op.clone()); - } - - model = applied_model; - } - - if transactions.is_empty() { - Ok(None) - } else { - Ok(Some(transactions)) - } - } - - fn apply_effects_and_submit( - &mut self, - connection: &mut PgConnection, - id: impl Into, - identity: AuthId, - to_apply: Vec, - applying_new_namespace: bool, - ) -> Result { - if applying_new_namespace { - self.submit(id, identity, to_apply) - } else if let Some(to_apply) = self.check_for_effects(connection, &to_apply)? { - self.submit(id, identity, to_apply) - } else { - info!("API call will not result in any data changes"); - let model = ProvModel::from_tx(&to_apply)?; - Ok(ApiResponse::already_recorded(id, model)) - } - } - - /// Ensures that the named namespace exists, returns an existing namespace, and a vector containing a `ChronicleTransaction` to create one if not present - /// - /// A namespace uri is of the form chronicle:ns:{external_id}:{uuid} - /// Namespaces must be globally unique, so are disambiguated by uuid but are locally referred to by external_id only - /// For coordination between chronicle nodes we also need a namespace binding operation to tie the UUID from another instance to a external_id - /// # Arguments - /// * `external_id` - an arbitrary namespace identifier - #[instrument(skip(self, connection))] - fn ensure_namespace( - &mut self, - connection: &mut PgConnection, - external_id: &ExternalId, - ) -> Result<(NamespaceId, Vec), ApiError> { - let ns = self.store.namespace_by_external_id(connection, external_id); - - if ns.is_err() { - debug!(?ns, "Namespace does not exist, creating"); - - let uuid = U::uuid(); - let id: NamespaceId = NamespaceId::from_external_id(external_id, uuid); - Ok(( - id.clone(), - vec![ChronicleOperation::CreateNamespace(CreateNamespace::new( - id, - external_id, - uuid, - ))], - )) - } else { - Ok((ns?.0, vec![])) - } - } - - /// Creates and submits a (ChronicleTransaction::GenerateEntity), and possibly (ChronicleTransaction::Domaintype) if specified - /// - /// We use our local store for a best guess at the activity, either by external_id or the last one started as a convenience for command line - #[instrument(skip(self))] - async fn activity_generate( - &self, - id: EntityId, - namespace: ExternalId, - activity_id: ActivityId, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let create = ChronicleOperation::WasGeneratedBy(WasGeneratedBy { - namespace, - id: id.clone(), - activity: activity_id, - }); - - to_apply.push(create); - - api.apply_effects_and_submit( - connection, - id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - /// Creates and submits a (ChronicleTransaction::ActivityUses), and possibly (ChronicleTransaction::Domaintype) if specified - /// We use our local store for a best guess at the activity, either by name or the last one started as a convenience for command line - #[instrument(skip(self))] - async fn activity_use( - &self, - id: EntityId, - namespace: ExternalId, - activity_id: ActivityId, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let (id, to_apply) = { - let create = ChronicleOperation::ActivityUses(ActivityUses { - namespace, - id: id.clone(), - activity: activity_id, - }); - - to_apply.push(create); - - (id, to_apply) - }; - - api.apply_effects_and_submit( - connection, - id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - /// Creates and submits a (ChronicleTransaction::ActivityWasInformedBy) - /// - /// We use our local store for a best guess at the activity, either by external_id or the last one started as a convenience for command line - #[instrument(skip(self))] - async fn activity_was_informed_by( - &self, - id: ActivityId, - namespace: ExternalId, - informing_activity_id: ActivityId, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let (id, to_apply) = { - let create = ChronicleOperation::WasInformedBy(WasInformedBy { - namespace, - activity: id.clone(), - informing_activity: informing_activity_id, - }); - - to_apply.push(create); - - (id, to_apply) - }; - - api.apply_effects_and_submit( - connection, - id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - /// Submits operations [`CreateEntity`], and [`SetAttributes::Entity`] - /// - /// We use our local store to see if the agent already exists, disambiguating the URI if so - #[instrument(skip(self))] - async fn create_entity( - &self, - external_id: ExternalId, - namespace: ExternalId, - attributes: Attributes, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let id = EntityId::from_external_id(&external_id); - - let create = ChronicleOperation::EntityExists(EntityExists { - namespace: namespace.clone(), - external_id: external_id.clone(), - }); - - to_apply.push(create); - - let set_type = ChronicleOperation::SetAttributes(SetAttributes::Entity { - id: EntityId::from_external_id(&external_id), - namespace, - attributes, - }); - - to_apply.push(set_type); - - api.apply_effects_and_submit( - connection, - id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - /// Submits operations [`CreateActivity`], and [`SetAttributes::Activity`] - /// - /// We use our local store to see if the activity already exists, disambiguating the URI if so - #[instrument(skip(self))] - async fn create_activity( - &self, - external_id: ExternalId, - namespace: ExternalId, - attributes: Attributes, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let create = ChronicleOperation::ActivityExists(ActivityExists { - namespace: namespace.clone(), - external_id: external_id.clone(), - }); - - to_apply.push(create); - - let id = ActivityId::from_external_id(&external_id); - let set_type = ChronicleOperation::SetAttributes(SetAttributes::Activity { - id: id.clone(), - namespace, - attributes, - }); - - to_apply.push(set_type); - - api.apply_effects_and_submit( - connection, - id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - /// Submits operations [`CreateAgent`], and [`SetAttributes::Agent`] - /// - /// We use our local store to see if the agent already exists, disambiguating the URI if so - #[instrument(skip(self))] - async fn create_agent( - &self, - external_id: ExternalId, - namespace: ExternalId, - attributes: Attributes, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let create = ChronicleOperation::AgentExists(AgentExists { - external_id: external_id.to_owned(), - namespace: namespace.clone(), - }); - - to_apply.push(create); - - let id = AgentId::from_external_id(&external_id); - let set_type = ChronicleOperation::SetAttributes(SetAttributes::Agent { - id: id.clone(), - namespace, - attributes, - }); - - to_apply.push(set_type); - - api.apply_effects_and_submit( - connection, - id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - /// Creates and submits a (ChronicleTransaction::CreateNamespace) if the external_id part does not already exist in local storage - async fn create_namespace( - &self, - external_id: &ExternalId, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - let external_id = external_id.to_owned(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - connection.build_transaction().run(|connection| { - let (namespace, to_apply) = api.ensure_namespace(connection, &external_id)?; - - api.submit(namespace, identity, to_apply) - }) - }) - .await? - } - - #[instrument(skip(self))] - async fn depth_charge( - &self, - namespace: NamespaceId, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - let id = ActivityId::from_external_id(Uuid::new_v4().to_string()); - tokio::task::spawn_blocking(move || { - let to_apply = vec![ - ChronicleOperation::StartActivity(StartActivity { - namespace: namespace.clone(), - id: id.clone(), - time: Utc::now(), - }), - ChronicleOperation::EndActivity(EndActivity { - namespace, - id, - time: Utc::now(), - }), - ]; - api.submit_depth_charge(identity, to_apply) - }) - .await? - } - - fn submit_depth_charge( - &mut self, - identity: AuthId, - to_apply: Vec, - ) -> Result { - let identity = identity.signed_identity(&self.signing)?; - let tx_id = self.submit_blocking(&ChronicleTransaction::new(to_apply, identity))?; - Ok(ApiResponse::depth_charge_submission(tx_id)) - } - - #[instrument(skip(self))] - async fn dispatch(&mut self, command: (ApiCommand, AuthId)) -> Result { - match command { - (ApiCommand::DepthCharge(DepthChargeCommand { namespace }), identity) => { - self.depth_charge(namespace, identity).await - } - ( - ApiCommand::Import(ImportCommand { - namespace, - operations, - }), - identity, - ) => { - self.submit_import_operations(identity, namespace, operations) - .await - } - (ApiCommand::NameSpace(NamespaceCommand::Create { external_id }), identity) => { - self.create_namespace(&external_id, identity).await - } - ( - ApiCommand::Agent(AgentCommand::Create { - external_id, - namespace, - attributes, - }), - identity, - ) => { - self.create_agent(external_id, namespace, attributes, identity) - .await - } - (ApiCommand::Agent(AgentCommand::UseInContext { id, namespace }), _identity) => { - self.use_agent_in_cli_context(id, namespace).await - } - ( - ApiCommand::Agent(AgentCommand::Delegate { - id, - delegate, - activity, - namespace, - role, - }), - identity, - ) => { - self.delegate(namespace, id, delegate, activity, role, identity) - .await - } - ( - ApiCommand::Activity(ActivityCommand::Create { - external_id, - namespace, - attributes, - }), - identity, - ) => { - self.create_activity(external_id, namespace, attributes, identity) - .await - } - ( - ApiCommand::Activity(ActivityCommand::Instant { - id, - namespace, - time, - agent, - }), - identity, - ) => self.instant(id, namespace, time, agent, identity).await, - ( - ApiCommand::Activity(ActivityCommand::Start { - id, - namespace, - time, - agent, - }), - identity, - ) => { - self.start_activity(id, namespace, time, agent, identity) - .await - } - ( - ApiCommand::Activity(ActivityCommand::End { - id, - namespace, - time, - agent, - }), - identity, - ) => { - self.end_activity(id, namespace, time, agent, identity) - .await - } - ( - ApiCommand::Activity(ActivityCommand::Use { - id, - namespace, - activity, - }), - identity, - ) => self.activity_use(id, namespace, activity, identity).await, - ( - ApiCommand::Activity(ActivityCommand::WasInformedBy { - id, - namespace, - informing_activity, - }), - identity, - ) => { - self.activity_was_informed_by(id, namespace, informing_activity, identity) - .await - } - ( - ApiCommand::Activity(ActivityCommand::Associate { - id, - namespace, - responsible, - role, - }), - identity, - ) => { - self.associate(namespace, responsible, id, role, identity) - .await - } - ( - ApiCommand::Entity(EntityCommand::Attribute { - id, - namespace, - responsible, - role, - }), - identity, - ) => { - self.attribute(namespace, responsible, id, role, identity) - .await - } - ( - ApiCommand::Entity(EntityCommand::Create { - external_id, - namespace, - attributes, - }), - identity, - ) => { - self.create_entity(external_id, namespace, attributes, identity) - .await - } - ( - ApiCommand::Activity(ActivityCommand::Generate { - id, - namespace, - activity, - }), - identity, - ) => { - self.activity_generate(id, namespace, activity, identity) - .await - } - ( - ApiCommand::Entity(EntityCommand::Derive { - id, - namespace, - activity, - used_entity, - derivation, - }), - identity, - ) => { - self.entity_derive(id, namespace, activity, used_entity, derivation, identity) - .await - } - (ApiCommand::Query(query), _identity) => self.query(query).await, - } - } - - #[instrument(skip(self))] - async fn delegate( - &self, - namespace: ExternalId, - responsible_id: AgentId, - delegate_id: AgentId, - activity_id: Option, - role: Option, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let tx = ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf::new( - &namespace, - &responsible_id, - &delegate_id, - activity_id.as_ref(), - role, - )); - - to_apply.push(tx); - - api.apply_effects_and_submit( - connection, - responsible_id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - #[instrument(skip(self))] - async fn associate( - &self, - namespace: ExternalId, - responsible_id: AgentId, - activity_id: ActivityId, - role: Option, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let tx = ChronicleOperation::WasAssociatedWith(WasAssociatedWith::new( - &namespace, - &activity_id, - &responsible_id, - role, - )); - - to_apply.push(tx); - - api.apply_effects_and_submit( - connection, - responsible_id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - #[instrument(skip(self))] - async fn attribute( - &self, - namespace: ExternalId, - responsible_id: AgentId, - entity_id: EntityId, - role: Option, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let tx = ChronicleOperation::WasAttributedTo(WasAttributedTo::new( - &namespace, - &entity_id, - &responsible_id, - role, - )); - - to_apply.push(tx); - - api.apply_effects_and_submit( - connection, - responsible_id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - #[instrument(skip(self))] - async fn entity_derive( - &self, - id: EntityId, - namespace: ExternalId, - activity_id: Option, - used_id: EntityId, - typ: DerivationType, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let tx = ChronicleOperation::EntityDerive(EntityDerive { - namespace, - id: id.clone(), - used_id: used_id.clone(), - activity_id: activity_id.clone(), - typ, - }); - - to_apply.push(tx); - - api.apply_effects_and_submit( - connection, - id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - async fn query(&self, query: QueryCommand) -> Result { - let api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - let (id, _) = api - .store - .namespace_by_external_id(&mut connection, &ExternalId::from(&query.namespace))?; - Ok(ApiResponse::query_reply( - api.store.prov_model_for_namespace(&mut connection, &id)?, - )) - }) - .await? - } - - async fn submit_import_operations( - &self, - identity: AuthId, - namespace: NamespaceId, - operations: Vec, - ) -> Result { - let mut api = self.clone(); - let identity = identity.signed_identity(&self.signing)?; - let model = ProvModel::from_tx(&operations)?; - tokio::task::spawn_blocking(move || { - // Check here to ensure that import operations result in data changes - let mut connection = api.store.connection()?; - connection.build_transaction().run(|connection| { - if let Some(operations_to_apply) = api.check_for_effects(connection, &operations)? { - info!("Submitting import operations to ledger"); - let tx_id = api.submit_blocking(&ChronicleTransaction::new( - operations_to_apply, - identity, - ))?; - Ok(ApiResponse::import_submitted(model, tx_id)) - } else { - info!("Import will not result in any data changes"); - let model = ProvModel::from_tx(&operations)?; - Ok(ApiResponse::already_recorded(namespace, model)) - } - }) - }) - .await? - } - - #[instrument(level = "debug", skip(self), ret(Debug))] - async fn sync( - &self, - prov: Box, - block_id: &BlockId, - tx_id: ChronicleTransactionId, - ) -> Result { - let api = self.clone(); - let block_id = *block_id; - tokio::task::spawn_blocking(move || { - api.store.apply_prov(&prov)?; - api.store.set_last_block_id(&block_id, tx_id)?; - - Ok(ApiResponse::Unit) - }) - .await? - } - - /// Creates and submits a (ChronicleTransaction::StartActivity) determining the appropriate agent by external_id, or via [use_agent] context - #[instrument(skip(self))] - async fn instant( - &self, - id: ActivityId, - namespace: ExternalId, - time: Option>, - agent: Option, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let agent_id = { - if let Some(agent) = agent { - Some(agent) - } else { - api.store - .get_current_agent(connection) - .ok() - .map(|x| AgentId::from_external_id(x.external_id)) - } - }; - - to_apply.push(ChronicleOperation::StartActivity(StartActivity { - namespace: namespace.clone(), - id: id.clone(), - time: time.unwrap_or_else(Utc::now), - })); - - to_apply.push(ChronicleOperation::EndActivity(EndActivity { - namespace: namespace.clone(), - id: id.clone(), - time: time.unwrap_or_else(Utc::now), - })); - - if let Some(agent_id) = agent_id { - to_apply.push(ChronicleOperation::WasAssociatedWith( - WasAssociatedWith::new(&namespace, &id, &agent_id, None), - )); - } - - api.apply_effects_and_submit( - connection, - id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - /// Creates and submits a (ChronicleTransaction::StartActivity), determining the appropriate agent by name, or via [use_agent] context - #[instrument(skip(self))] - async fn start_activity( - &self, - id: ActivityId, - namespace: ExternalId, - time: Option>, - agent: Option, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let agent_id = { - if let Some(agent) = agent { - Some(agent) - } else { - api.store - .get_current_agent(connection) - .ok() - .map(|x| AgentId::from_external_id(x.external_id)) - } - }; - - to_apply.push(ChronicleOperation::StartActivity(StartActivity { - namespace: namespace.clone(), - id: id.clone(), - time: time.unwrap_or_else(Utc::now), - })); - - if let Some(agent_id) = agent_id { - to_apply.push(ChronicleOperation::WasAssociatedWith( - WasAssociatedWith::new(&namespace, &id, &agent_id, None), - )); - } - - api.apply_effects_and_submit( - connection, - id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - /// Creates and submits a (ChronicleTransaction::EndActivity), determining the appropriate agent by name or via [use_agent] context - #[instrument(skip(self))] - async fn end_activity( - &self, - id: ActivityId, - namespace: ExternalId, - time: Option>, - agent: Option, - identity: AuthId, - ) -> Result { - let mut api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - connection.build_transaction().run(|connection| { - let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; - - let applying_new_namespace = !to_apply.is_empty(); - - let agent_id = { - if let Some(agent) = agent { - Some(agent) - } else { - api.store - .get_current_agent(connection) - .ok() - .map(|x| AgentId::from_external_id(x.external_id)) - } - }; - - to_apply.push(ChronicleOperation::EndActivity(EndActivity { - namespace: namespace.clone(), - id: id.clone(), - time: time.unwrap_or_else(Utc::now), - })); - - if let Some(agent_id) = agent_id { - to_apply.push(ChronicleOperation::WasAssociatedWith( - WasAssociatedWith::new(&namespace, &id, &agent_id, None), - )); - } - - api.apply_effects_and_submit( - connection, - id, - identity, - to_apply, - applying_new_namespace, - ) - }) - }) - .await? - } - - #[instrument(skip(self))] - async fn use_agent_in_cli_context( - &self, - id: AgentId, - namespace: ExternalId, - ) -> Result { - let api = self.clone(); - tokio::task::spawn_blocking(move || { - let mut connection = api.store.connection()?; - - connection.build_transaction().run(|connection| { - api.store - .use_agent(connection, id.external_id_part(), &namespace) - })?; - - Ok(ApiResponse::Unit) - }) - .await? - } + break; + } + }, + SubmissionStage::Committed(commit, _) => { + if commit.tx_id == tx_id { + let end_time = Instant::now(); + let elapsed_time = end_time - start_time; + + debug!( + "Depth charge transaction committed: {}", + commit.tx_id + ); + debug!( + "Depth charge round trip time: {:.2?}", + elapsed_time + ); + histogram!( + "depth_charge_round_trip", + elapsed_time.as_millis() as f64 + ); + break; + } + }, + SubmissionStage::NotCommitted((id, contradiction, _)) => { + if id == tx_id { + error!("Depth charge transaction rejected by ledger: {id} {contradiction}"); + break; + } + }, + } + } + }, + Ok(res) => error!("Unexpected ApiResponse from depth charge: {res:?}"), + Err(e) => error!("ApiError submitting depth charge: {e}"), + } + } + }); + } + + Ok(dispatch) + } + + /// Notify after a successful submission, for now this makes little + /// difference, but with the future introduction of a submission queue, + /// submission notifications will be decoupled from api invocation. + /// This is a measure to keep the api interface stable once this is introduced + fn submit_blocking( + &mut self, + tx: &ChronicleTransaction, + ) -> Result { + let res = self.ledger_writer.submit(&ChronicleSubmitTransaction { + tx: tx.clone(), + signer: self.signing.clone(), + policy_name: self.policy_name.clone(), + }); + + match res { + Ok(tx_id) => { + let tx_id = ChronicleTransactionId::from(tx_id.as_str()); + self.submit_tx.send(SubmissionStage::submitted(&tx_id)).ok(); + Ok(tx_id) + }, + Err((Some(tx_id), e)) => { + // We need the cloneable SubmissionError wrapper here + let submission_error = SubmissionError::communication( + &ChronicleTransactionId::from(tx_id.as_str()), + e, + ); + self.submit_tx.send(SubmissionStage::submitted_error(&submission_error)).ok(); + Err(submission_error.into()) + }, + Err((None, e)) => Err(e.into()), + } + } + + /// Generate and submit the signed identity to send to the Transaction Processor along with the + /// transactions to be applied + fn submit( + &mut self, + id: impl Into, + identity: AuthId, + to_apply: Vec, + ) -> Result { + let identity = identity.signed_identity(&self.signing)?; + let model = ProvModel::from_tx(&to_apply)?; + let tx_id = self.submit_blocking(&ChronicleTransaction::new(to_apply, identity))?; + + Ok(ApiResponse::submission(id, model, tx_id)) + } + + /// Checks if ChronicleOperations resulting from Chronicle API calls will result in any changes + /// in state + /// + /// # Arguments + /// * `connection` - Connection to the Chronicle database + /// * `to_apply` - Chronicle operations resulting from an API call + #[instrument(skip(self, connection))] + fn check_for_effects( + &mut self, + connection: &mut PgConnection, + to_apply: &Vec, + ) -> Result>, ApiError> { + let mut model = ProvModel::default(); + let mut transactions = Vec::::with_capacity(to_apply.len()); + for op in to_apply { + let mut applied_model = match op { + ChronicleOperation::CreateNamespace(CreateNamespace { external_id, .. }) => { + let (namespace, _) = self.ensure_namespace(connection, external_id)?; + model.namespace_context(&namespace); + model + }, + ChronicleOperation::AgentExists(AgentExists { ref namespace, ref external_id }) => { + model.namespace_context(namespace); + self.store.apply_prov_model_for_agent_id( + connection, + model, + &AgentId::from_external_id(external_id), + namespace.external_id_part(), + )? + }, + ChronicleOperation::ActivityExists(ActivityExists { + ref namespace, + ref external_id, + }) => { + model.namespace_context(namespace); + + self.store.apply_prov_model_for_activity_id( + connection, + model, + &ActivityId::from_external_id(external_id), + namespace.external_id_part(), + )? + }, + ChronicleOperation::EntityExists(EntityExists { + ref namespace, + ref external_id, + }) => { + model.namespace_context(namespace); + self.store.apply_prov_model_for_entity_id( + connection, + model, + &EntityId::from_external_id(external_id), + namespace.external_id_part(), + )? + }, + ChronicleOperation::ActivityUses(ActivityUses { + ref namespace, + ref id, + ref activity, + }) => { + model.namespace_context(namespace); + self.store.prov_model_for_usage( + connection, + model, + id, + activity, + namespace.external_id_part(), + )? + }, + ChronicleOperation::SetAttributes(ref o) => match o { + SetAttributes::Activity { namespace, id, .. } => { + model.namespace_context(namespace); + self.store.apply_prov_model_for_activity_id( + connection, + model, + id, + namespace.external_id_part(), + )? + }, + SetAttributes::Agent { namespace, id, .. } => { + model.namespace_context(namespace); + self.store.apply_prov_model_for_agent_id( + connection, + model, + id, + namespace.external_id_part(), + )? + }, + SetAttributes::Entity { namespace, id, .. } => { + model.namespace_context(namespace); + self.store.apply_prov_model_for_entity_id( + connection, + model, + id, + namespace.external_id_part(), + )? + }, + }, + ChronicleOperation::StartActivity(StartActivity { namespace, id, .. }) => { + model.namespace_context(namespace); + self.store.apply_prov_model_for_activity_id( + connection, + model, + id, + namespace.external_id_part(), + )? + }, + ChronicleOperation::EndActivity(EndActivity { namespace, id, .. }) => { + model.namespace_context(namespace); + self.store.apply_prov_model_for_activity_id( + connection, + model, + id, + namespace.external_id_part(), + )? + }, + ChronicleOperation::WasInformedBy(WasInformedBy { + namespace, + activity, + informing_activity, + }) => { + model.namespace_context(namespace); + let model = self.store.apply_prov_model_for_activity_id( + connection, + model, + activity, + namespace.external_id_part(), + )?; + self.store.apply_prov_model_for_activity_id( + connection, + model, + informing_activity, + namespace.external_id_part(), + )? + }, + ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf { + activity_id, + responsible_id, + delegate_id, + namespace, + .. + }) => { + model.namespace_context(namespace); + let model = self.store.apply_prov_model_for_agent_id( + connection, + model, + responsible_id, + namespace.external_id_part(), + )?; + let model = self.store.apply_prov_model_for_agent_id( + connection, + model, + delegate_id, + namespace.external_id_part(), + )?; + if let Some(id) = activity_id { + self.store.apply_prov_model_for_activity_id( + connection, + model, + id, + namespace.external_id_part(), + )? + } else { + model + } + }, + ChronicleOperation::WasAssociatedWith(WasAssociatedWith { + namespace, + activity_id, + agent_id, + .. + }) => { + model.namespace_context(namespace); + let model = self.store.apply_prov_model_for_activity_id( + connection, + model, + activity_id, + namespace.external_id_part(), + )?; + + self.store.apply_prov_model_for_agent_id( + connection, + model, + agent_id, + namespace.external_id_part(), + )? + }, + ChronicleOperation::WasGeneratedBy(WasGeneratedBy { namespace, id, activity }) => { + model.namespace_context(namespace); + let model = self.store.apply_prov_model_for_activity_id( + connection, + model, + activity, + namespace.external_id_part(), + )?; + + self.store.apply_prov_model_for_entity_id( + connection, + model, + id, + namespace.external_id_part(), + )? + }, + ChronicleOperation::EntityDerive(EntityDerive { + namespace, + id, + used_id, + activity_id, + .. + }) => { + model.namespace_context(namespace); + let model = self.store.apply_prov_model_for_entity_id( + connection, + model, + id, + namespace.external_id_part(), + )?; + + let model = self.store.apply_prov_model_for_entity_id( + connection, + model, + used_id, + namespace.external_id_part(), + )?; + + if let Some(id) = activity_id { + self.store.apply_prov_model_for_activity_id( + connection, + model, + id, + namespace.external_id_part(), + )? + } else { + model + } + }, + ChronicleOperation::WasAttributedTo(WasAttributedTo { + namespace, + entity_id, + agent_id, + .. + }) => { + model.namespace_context(namespace); + let model = self.store.apply_prov_model_for_entity_id( + connection, + model, + entity_id, + namespace.external_id_part(), + )?; + + self.store.apply_prov_model_for_agent_id( + connection, + model, + agent_id, + namespace.external_id_part(), + )? + }, + }; + let state = applied_model.clone(); + applied_model.apply(op)?; + if state != applied_model { + transactions.push(op.clone()); + } + + model = applied_model; + } + + if transactions.is_empty() { + Ok(None) + } else { + Ok(Some(transactions)) + } + } + + fn apply_effects_and_submit( + &mut self, + connection: &mut PgConnection, + id: impl Into, + identity: AuthId, + to_apply: Vec, + applying_new_namespace: bool, + ) -> Result { + if applying_new_namespace { + self.submit(id, identity, to_apply) + } else if let Some(to_apply) = self.check_for_effects(connection, &to_apply)? { + self.submit(id, identity, to_apply) + } else { + info!("API call will not result in any data changes"); + let model = ProvModel::from_tx(&to_apply)?; + Ok(ApiResponse::already_recorded(id, model)) + } + } + + /// Ensures that the named namespace exists, returns an existing namespace, and a vector + /// containing a `ChronicleTransaction` to create one if not present + /// + /// A namespace uri is of the form chronicle:ns:{external_id}:{uuid} + /// Namespaces must be globally unique, so are disambiguated by uuid but are locally referred to + /// by external_id only For coordination between chronicle nodes we also need a namespace + /// binding operation to tie the UUID from another instance to a external_id # Arguments + /// * `external_id` - an arbitrary namespace identifier + #[instrument(skip(self, connection))] + fn ensure_namespace( + &mut self, + connection: &mut PgConnection, + external_id: &ExternalId, + ) -> Result<(NamespaceId, Vec), ApiError> { + let ns = self.store.namespace_by_external_id(connection, external_id); + + if ns.is_err() { + debug!(?ns, "Namespace does not exist, creating"); + + let uuid = U::uuid(); + let id: NamespaceId = NamespaceId::from_external_id(external_id, uuid); + Ok(( + id.clone(), + vec![ChronicleOperation::CreateNamespace(CreateNamespace::new( + id, + external_id, + uuid, + ))], + )) + } else { + Ok((ns?.0, vec![])) + } + } + + /// Creates and submits a (ChronicleTransaction::GenerateEntity), and possibly + /// (ChronicleTransaction::Domaintype) if specified + /// + /// We use our local store for a best guess at the activity, either by external_id or the last + /// one started as a convenience for command line + #[instrument(skip(self))] + async fn activity_generate( + &self, + id: EntityId, + namespace: ExternalId, + activity_id: ActivityId, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let create = ChronicleOperation::WasGeneratedBy(WasGeneratedBy { + namespace, + id: id.clone(), + activity: activity_id, + }); + + to_apply.push(create); + + api.apply_effects_and_submit( + connection, + id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + /// Creates and submits a (ChronicleTransaction::ActivityUses), and possibly + /// (ChronicleTransaction::Domaintype) if specified We use our local store for a best guess at + /// the activity, either by name or the last one started as a convenience for command line + #[instrument(skip(self))] + async fn activity_use( + &self, + id: EntityId, + namespace: ExternalId, + activity_id: ActivityId, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let (id, to_apply) = { + let create = ChronicleOperation::ActivityUses(ActivityUses { + namespace, + id: id.clone(), + activity: activity_id, + }); + + to_apply.push(create); + + (id, to_apply) + }; + + api.apply_effects_and_submit( + connection, + id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + /// Creates and submits a (ChronicleTransaction::ActivityWasInformedBy) + /// + /// We use our local store for a best guess at the activity, either by external_id or the last + /// one started as a convenience for command line + #[instrument(skip(self))] + async fn activity_was_informed_by( + &self, + id: ActivityId, + namespace: ExternalId, + informing_activity_id: ActivityId, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let (id, to_apply) = { + let create = ChronicleOperation::WasInformedBy(WasInformedBy { + namespace, + activity: id.clone(), + informing_activity: informing_activity_id, + }); + + to_apply.push(create); + + (id, to_apply) + }; + + api.apply_effects_and_submit( + connection, + id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + /// Submits operations [`CreateEntity`], and [`SetAttributes::Entity`] + /// + /// We use our local store to see if the agent already exists, disambiguating the URI if so + #[instrument(skip(self))] + async fn create_entity( + &self, + external_id: ExternalId, + namespace: ExternalId, + attributes: Attributes, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let id = EntityId::from_external_id(&external_id); + + let create = ChronicleOperation::EntityExists(EntityExists { + namespace: namespace.clone(), + external_id: external_id.clone(), + }); + + to_apply.push(create); + + let set_type = ChronicleOperation::SetAttributes(SetAttributes::Entity { + id: EntityId::from_external_id(&external_id), + namespace, + attributes, + }); + + to_apply.push(set_type); + + api.apply_effects_and_submit( + connection, + id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + /// Submits operations [`CreateActivity`], and [`SetAttributes::Activity`] + /// + /// We use our local store to see if the activity already exists, disambiguating the URI if so + #[instrument(skip(self))] + async fn create_activity( + &self, + external_id: ExternalId, + namespace: ExternalId, + attributes: Attributes, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let create = ChronicleOperation::ActivityExists(ActivityExists { + namespace: namespace.clone(), + external_id: external_id.clone(), + }); + + to_apply.push(create); + + let id = ActivityId::from_external_id(&external_id); + let set_type = ChronicleOperation::SetAttributes(SetAttributes::Activity { + id: id.clone(), + namespace, + attributes, + }); + + to_apply.push(set_type); + + api.apply_effects_and_submit( + connection, + id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + /// Submits operations [`CreateAgent`], and [`SetAttributes::Agent`] + /// + /// We use our local store to see if the agent already exists, disambiguating the URI if so + #[instrument(skip(self))] + async fn create_agent( + &self, + external_id: ExternalId, + namespace: ExternalId, + attributes: Attributes, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let create = ChronicleOperation::AgentExists(AgentExists { + external_id: external_id.to_owned(), + namespace: namespace.clone(), + }); + + to_apply.push(create); + + let id = AgentId::from_external_id(&external_id); + let set_type = ChronicleOperation::SetAttributes(SetAttributes::Agent { + id: id.clone(), + namespace, + attributes, + }); + + to_apply.push(set_type); + + api.apply_effects_and_submit( + connection, + id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + /// Creates and submits a (ChronicleTransaction::CreateNamespace) if the external_id part does + /// not already exist in local storage + async fn create_namespace( + &self, + external_id: &ExternalId, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + let external_id = external_id.to_owned(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + connection.build_transaction().run(|connection| { + let (namespace, to_apply) = api.ensure_namespace(connection, &external_id)?; + + api.submit(namespace, identity, to_apply) + }) + }) + .await? + } + + #[instrument(skip(self))] + async fn depth_charge( + &self, + namespace: NamespaceId, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + let id = ActivityId::from_external_id(Uuid::new_v4().to_string()); + tokio::task::spawn_blocking(move || { + let to_apply = vec![ + ChronicleOperation::StartActivity(StartActivity { + namespace: namespace.clone(), + id: id.clone(), + time: Utc::now(), + }), + ChronicleOperation::EndActivity(EndActivity { namespace, id, time: Utc::now() }), + ]; + api.submit_depth_charge(identity, to_apply) + }) + .await? + } + + fn submit_depth_charge( + &mut self, + identity: AuthId, + to_apply: Vec, + ) -> Result { + let identity = identity.signed_identity(&self.signing)?; + let tx_id = self.submit_blocking(&ChronicleTransaction::new(to_apply, identity))?; + Ok(ApiResponse::depth_charge_submission(tx_id)) + } + + #[instrument(skip(self))] + async fn dispatch(&mut self, command: (ApiCommand, AuthId)) -> Result { + match command { + (ApiCommand::DepthCharge(DepthChargeCommand { namespace }), identity) => { + self.depth_charge(namespace, identity).await + }, + (ApiCommand::Import(ImportCommand { namespace, operations }), identity) => { + self.submit_import_operations(identity, namespace, operations).await + }, + (ApiCommand::NameSpace(NamespaceCommand::Create { external_id }), identity) => { + self.create_namespace(&external_id, identity).await + }, + ( + ApiCommand::Agent(AgentCommand::Create { external_id, namespace, attributes }), + identity, + ) => self.create_agent(external_id, namespace, attributes, identity).await, + (ApiCommand::Agent(AgentCommand::UseInContext { id, namespace }), _identity) => { + self.use_agent_in_cli_context(id, namespace).await + }, + ( + ApiCommand::Agent(AgentCommand::Delegate { + id, + delegate, + activity, + namespace, + role, + }), + identity, + ) => self.delegate(namespace, id, delegate, activity, role, identity).await, + ( + ApiCommand::Activity(ActivityCommand::Create { + external_id, + namespace, + attributes, + }), + identity, + ) => self.create_activity(external_id, namespace, attributes, identity).await, + ( + ApiCommand::Activity(ActivityCommand::Instant { id, namespace, time, agent }), + identity, + ) => self.instant(id, namespace, time, agent, identity).await, + ( + ApiCommand::Activity(ActivityCommand::Start { id, namespace, time, agent }), + identity, + ) => self.start_activity(id, namespace, time, agent, identity).await, + ( + ApiCommand::Activity(ActivityCommand::End { id, namespace, time, agent }), + identity, + ) => self.end_activity(id, namespace, time, agent, identity).await, + (ApiCommand::Activity(ActivityCommand::Use { id, namespace, activity }), identity) => { + self.activity_use(id, namespace, activity, identity).await + }, + ( + ApiCommand::Activity(ActivityCommand::WasInformedBy { + id, + namespace, + informing_activity, + }), + identity, + ) => self.activity_was_informed_by(id, namespace, informing_activity, identity).await, + ( + ApiCommand::Activity(ActivityCommand::Associate { + id, + namespace, + responsible, + role, + }), + identity, + ) => self.associate(namespace, responsible, id, role, identity).await, + ( + ApiCommand::Entity(EntityCommand::Attribute { id, namespace, responsible, role }), + identity, + ) => self.attribute(namespace, responsible, id, role, identity).await, + ( + ApiCommand::Entity(EntityCommand::Create { external_id, namespace, attributes }), + identity, + ) => self.create_entity(external_id, namespace, attributes, identity).await, + ( + ApiCommand::Activity(ActivityCommand::Generate { id, namespace, activity }), + identity, + ) => self.activity_generate(id, namespace, activity, identity).await, + ( + ApiCommand::Entity(EntityCommand::Derive { + id, + namespace, + activity, + used_entity, + derivation, + }), + identity, + ) => { + self.entity_derive(id, namespace, activity, used_entity, derivation, identity) + .await + }, + (ApiCommand::Query(query), _identity) => self.query(query).await, + } + } + + #[instrument(skip(self))] + async fn delegate( + &self, + namespace: ExternalId, + responsible_id: AgentId, + delegate_id: AgentId, + activity_id: Option, + role: Option, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let tx = ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf::new( + &namespace, + &responsible_id, + &delegate_id, + activity_id.as_ref(), + role, + )); + + to_apply.push(tx); + + api.apply_effects_and_submit( + connection, + responsible_id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + #[instrument(skip(self))] + async fn associate( + &self, + namespace: ExternalId, + responsible_id: AgentId, + activity_id: ActivityId, + role: Option, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let tx = ChronicleOperation::WasAssociatedWith(WasAssociatedWith::new( + &namespace, + &activity_id, + &responsible_id, + role, + )); + + to_apply.push(tx); + + api.apply_effects_and_submit( + connection, + responsible_id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + #[instrument(skip(self))] + async fn attribute( + &self, + namespace: ExternalId, + responsible_id: AgentId, + entity_id: EntityId, + role: Option, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let tx = ChronicleOperation::WasAttributedTo(WasAttributedTo::new( + &namespace, + &entity_id, + &responsible_id, + role, + )); + + to_apply.push(tx); + + api.apply_effects_and_submit( + connection, + responsible_id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + #[instrument(skip(self))] + async fn entity_derive( + &self, + id: EntityId, + namespace: ExternalId, + activity_id: Option, + used_id: EntityId, + typ: DerivationType, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let tx = ChronicleOperation::EntityDerive(EntityDerive { + namespace, + id: id.clone(), + used_id: used_id.clone(), + activity_id: activity_id.clone(), + typ, + }); + + to_apply.push(tx); + + api.apply_effects_and_submit( + connection, + id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + async fn query(&self, query: QueryCommand) -> Result { + let api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + let (id, _) = api + .store + .namespace_by_external_id(&mut connection, &ExternalId::from(&query.namespace))?; + Ok(ApiResponse::query_reply(api.store.prov_model_for_namespace(&mut connection, &id)?)) + }) + .await? + } + + async fn submit_import_operations( + &self, + identity: AuthId, + namespace: NamespaceId, + operations: Vec, + ) -> Result { + let mut api = self.clone(); + let identity = identity.signed_identity(&self.signing)?; + let model = ProvModel::from_tx(&operations)?; + tokio::task::spawn_blocking(move || { + // Check here to ensure that import operations result in data changes + let mut connection = api.store.connection()?; + connection.build_transaction().run(|connection| { + if let Some(operations_to_apply) = api.check_for_effects(connection, &operations)? { + info!("Submitting import operations to ledger"); + let tx_id = api.submit_blocking(&ChronicleTransaction::new( + operations_to_apply, + identity, + ))?; + Ok(ApiResponse::import_submitted(model, tx_id)) + } else { + info!("Import will not result in any data changes"); + let model = ProvModel::from_tx(&operations)?; + Ok(ApiResponse::already_recorded(namespace, model)) + } + }) + }) + .await? + } + + #[instrument(level = "debug", skip(self), ret(Debug))] + async fn sync( + &self, + prov: Box, + block_id: &BlockId, + tx_id: ChronicleTransactionId, + ) -> Result { + let api = self.clone(); + let block_id = *block_id; + tokio::task::spawn_blocking(move || { + api.store.apply_prov(&prov)?; + api.store.set_last_block_id(&block_id, tx_id)?; + + Ok(ApiResponse::Unit) + }) + .await? + } + + /// Creates and submits a (ChronicleTransaction::StartActivity) determining the appropriate + /// agent by external_id, or via [use_agent] context + #[instrument(skip(self))] + async fn instant( + &self, + id: ActivityId, + namespace: ExternalId, + time: Option>, + agent: Option, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let agent_id = { + if let Some(agent) = agent { + Some(agent) + } else { + api.store + .get_current_agent(connection) + .ok() + .map(|x| AgentId::from_external_id(x.external_id)) + } + }; + + to_apply.push(ChronicleOperation::StartActivity(StartActivity { + namespace: namespace.clone(), + id: id.clone(), + time: time.unwrap_or_else(Utc::now), + })); + + to_apply.push(ChronicleOperation::EndActivity(EndActivity { + namespace: namespace.clone(), + id: id.clone(), + time: time.unwrap_or_else(Utc::now), + })); + + if let Some(agent_id) = agent_id { + to_apply.push(ChronicleOperation::WasAssociatedWith(WasAssociatedWith::new( + &namespace, &id, &agent_id, None, + ))); + } + + api.apply_effects_and_submit( + connection, + id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + /// Creates and submits a (ChronicleTransaction::StartActivity), determining the appropriate + /// agent by name, or via [use_agent] context + #[instrument(skip(self))] + async fn start_activity( + &self, + id: ActivityId, + namespace: ExternalId, + time: Option>, + agent: Option, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let agent_id = { + if let Some(agent) = agent { + Some(agent) + } else { + api.store + .get_current_agent(connection) + .ok() + .map(|x| AgentId::from_external_id(x.external_id)) + } + }; + + to_apply.push(ChronicleOperation::StartActivity(StartActivity { + namespace: namespace.clone(), + id: id.clone(), + time: time.unwrap_or_else(Utc::now), + })); + + if let Some(agent_id) = agent_id { + to_apply.push(ChronicleOperation::WasAssociatedWith(WasAssociatedWith::new( + &namespace, &id, &agent_id, None, + ))); + } + + api.apply_effects_and_submit( + connection, + id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + /// Creates and submits a (ChronicleTransaction::EndActivity), determining the appropriate agent + /// by name or via [use_agent] context + #[instrument(skip(self))] + async fn end_activity( + &self, + id: ActivityId, + namespace: ExternalId, + time: Option>, + agent: Option, + identity: AuthId, + ) -> Result { + let mut api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + connection.build_transaction().run(|connection| { + let (namespace, mut to_apply) = api.ensure_namespace(connection, &namespace)?; + + let applying_new_namespace = !to_apply.is_empty(); + + let agent_id = { + if let Some(agent) = agent { + Some(agent) + } else { + api.store + .get_current_agent(connection) + .ok() + .map(|x| AgentId::from_external_id(x.external_id)) + } + }; + + to_apply.push(ChronicleOperation::EndActivity(EndActivity { + namespace: namespace.clone(), + id: id.clone(), + time: time.unwrap_or_else(Utc::now), + })); + + if let Some(agent_id) = agent_id { + to_apply.push(ChronicleOperation::WasAssociatedWith(WasAssociatedWith::new( + &namespace, &id, &agent_id, None, + ))); + } + + api.apply_effects_and_submit( + connection, + id, + identity, + to_apply, + applying_new_namespace, + ) + }) + }) + .await? + } + + #[instrument(skip(self))] + async fn use_agent_in_cli_context( + &self, + id: AgentId, + namespace: ExternalId, + ) -> Result { + let api = self.clone(); + tokio::task::spawn_blocking(move || { + let mut connection = api.store.connection()?; + + connection.build_transaction().run(|connection| { + api.store.use_agent(connection, id.external_id_part(), &namespace) + })?; + + Ok(ApiResponse::Unit) + }) + .await? + } } #[cfg(test)] mod test { - use crate::{inmem::EmbeddedChronicleTp, Api, ApiDispatch, ApiError, UuidGen}; - - use chronicle_signing::{ - chronicle_secret_names, ChronicleSecretsOptions, ChronicleSigning, BATCHER_NAMESPACE, - CHRONICLE_NAMESPACE, - }; - use chrono::{TimeZone, Utc}; - use common::{ - attributes::{Attribute, Attributes}, - commands::{ - ActivityCommand, AgentCommand, ApiCommand, ApiResponse, EntityCommand, ImportCommand, - NamespaceCommand, - }, - database::TemporaryDatabase, - identity::AuthId, - k256::sha2::{Digest, Sha256}, - prov::{ - operations::{ChronicleOperation, DerivationType}, - to_json_ld::ToJson, - ActivityId, AgentId, ChronicleTransactionId, DomaintypeId, EntityId, NamespaceId, - ProvModel, - }, - }; - use opa_tp_protocol::state::{policy_address, policy_meta_address, PolicyMeta}; - use protobuf::Message; - use sawtooth_sdk::messages::setting::{Setting, Setting_Entry}; - - use uuid::Uuid; - - struct TestDispatch<'a> { - api: ApiDispatch, - _db: TemporaryDatabase<'a>, // share lifetime - _tp: EmbeddedChronicleTp, - } - - impl<'a> TestDispatch<'a> { - pub async fn dispatch( - &mut self, - command: ApiCommand, - identity: AuthId, - ) -> Result, ChronicleTransactionId)>, ApiError> { - // We can sort of get final on chain state here by using a map of subject to model - match self.api.dispatch(command, identity).await? { - ApiResponse::Submission { .. } | ApiResponse::ImportSubmitted { .. } => { - // Recv until we get a commit notification - loop { - let commit = self.api.notify_commit.subscribe().recv().await.unwrap(); - match commit { - common::ledger::SubmissionStage::Submitted(Ok(_)) => continue, - common::ledger::SubmissionStage::Committed(commit, _id) => { - return Ok(Some((commit.delta, commit.tx_id))) - } - common::ledger::SubmissionStage::Submitted(Err(e)) => panic!("{e:?}"), - common::ledger::SubmissionStage::NotCommitted((_, tx, _id)) => { - panic!("{tx:?}") - } - } - } - } - ApiResponse::AlreadyRecorded { subject: _, prov } => { - Ok(Some((prov, ChronicleTransactionId::from("null")))) - } - _ => Ok(None), - } - } - } - - #[derive(Debug, Clone)] - struct SameUuid; - - impl UuidGen for SameUuid { - fn uuid() -> Uuid { - Uuid::parse_str("5a0ab5b8-eeb7-4812-9fe3-6dd69bd20cea").unwrap() - } - } - - fn embed_chronicle_tp() -> EmbeddedChronicleTp { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - let mut buf = vec![]; - Setting { - entries: vec![Setting_Entry { - key: "chronicle.opa.policy_name".to_string(), - value: "allow_transactions".to_string(), - ..Default::default() - }] - .into(), - ..Default::default() - } - .write_to_vec(&mut buf) - .unwrap(); - let setting_id = ( - chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.policy_name"), - buf, - ); - let mut buf = vec![]; - Setting { - entries: vec![Setting_Entry { - key: "chronicle.opa.entrypoint".to_string(), - value: "allow_transactions.allowed_users".to_string(), - ..Default::default() - }] - .into(), - ..Default::default() - } - .write_to_vec(&mut buf) - .unwrap(); - - let setting_entrypoint = ( - chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.entrypoint"), - buf, - ); - - let d = env!("CARGO_MANIFEST_DIR").to_owned() + "/../../policies/bundle.tar.gz"; - let bin = std::fs::read(d).unwrap(); - - let meta = PolicyMeta { - id: "allow_transactions".to_string(), - hash: hex::encode(Sha256::digest(&bin)), - policy_address: policy_address("allow_transactions"), - }; - - EmbeddedChronicleTp::new_with_state( - vec![ - setting_id, - setting_entrypoint, - (policy_address("allow_transactions"), bin), - ( - policy_meta_address("allow_transactions"), - serde_json::to_vec(&meta).unwrap(), - ), - ] - .into_iter() - .collect(), - ) - .unwrap() - } - - async fn test_api<'a>() -> TestDispatch<'a> { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - - let secrets = ChronicleSigning::new( - chronicle_secret_names(), - vec![ - ( - CHRONICLE_NAMESPACE.to_string(), - ChronicleSecretsOptions::generate_in_memory(), - ), - ( - BATCHER_NAMESPACE.to_string(), - ChronicleSecretsOptions::generate_in_memory(), - ), - ], - ) - .await - .unwrap(); - let embed_tp = embed_chronicle_tp(); - let database = TemporaryDatabase::default(); - let pool = database.connection_pool().unwrap(); - - let liveness_check_interval = None; - - let dispatch = Api::new( - pool, - embed_tp.ledger.clone(), - SameUuid, - secrets, - vec![], - Some("allow_transactions".into()), - liveness_check_interval, - ) - .await - .unwrap(); - - TestDispatch { - api: dispatch, - _db: database, // share the lifetime - _tp: embed_tp, - } - } - - // Creates a mock file containing JSON-LD of the ChronicleOperations - // that would be created by the given command, although not in any particular order. - fn test_create_agent_operations_import() -> assert_fs::NamedTempFile { - let file = assert_fs::NamedTempFile::new("import.json").unwrap(); - assert_fs::prelude::FileWriteStr::write_str( - &file, - r#" + use crate::{inmem::EmbeddedChronicleTp, Api, ApiDispatch, ApiError, UuidGen}; + + use chronicle_signing::{ + chronicle_secret_names, ChronicleSecretsOptions, ChronicleSigning, BATCHER_NAMESPACE, + CHRONICLE_NAMESPACE, + }; + use chrono::{TimeZone, Utc}; + use common::{ + attributes::{Attribute, Attributes}, + commands::{ + ActivityCommand, AgentCommand, ApiCommand, ApiResponse, EntityCommand, ImportCommand, + NamespaceCommand, + }, + database::TemporaryDatabase, + identity::AuthId, + k256::sha2::{Digest, Sha256}, + prov::{ + operations::{ChronicleOperation, DerivationType}, + to_json_ld::ToJson, + ActivityId, AgentId, ChronicleTransactionId, DomaintypeId, EntityId, NamespaceId, + ProvModel, + }, + }; + use opa_tp_protocol::state::{policy_address, policy_meta_address, PolicyMeta}; + use protobuf::Message; + use sawtooth_sdk::messages::setting::{Setting, Setting_Entry}; + + use uuid::Uuid; + + struct TestDispatch<'a> { + api: ApiDispatch, + _db: TemporaryDatabase<'a>, // share lifetime + _tp: EmbeddedChronicleTp, + } + + impl<'a> TestDispatch<'a> { + pub async fn dispatch( + &mut self, + command: ApiCommand, + identity: AuthId, + ) -> Result, ChronicleTransactionId)>, ApiError> { + // We can sort of get final on chain state here by using a map of subject to model + match self.api.dispatch(command, identity).await? { + ApiResponse::Submission { .. } | ApiResponse::ImportSubmitted { .. } => { + // Recv until we get a commit notification + loop { + let commit = self.api.notify_commit.subscribe().recv().await.unwrap(); + match commit { + common::ledger::SubmissionStage::Submitted(Ok(_)) => continue, + common::ledger::SubmissionStage::Committed(commit, _id) => { + return Ok(Some((commit.delta, commit.tx_id))) + }, + common::ledger::SubmissionStage::Submitted(Err(e)) => panic!("{e:?}"), + common::ledger::SubmissionStage::NotCommitted((_, tx, _id)) => { + panic!("{tx:?}") + }, + } + } + }, + ApiResponse::AlreadyRecorded { subject: _, prov } => { + Ok(Some((prov, ChronicleTransactionId::from("null")))) + }, + _ => Ok(None), + } + } + } + + #[derive(Debug, Clone)] + struct SameUuid; + + impl UuidGen for SameUuid { + fn uuid() -> Uuid { + Uuid::parse_str("5a0ab5b8-eeb7-4812-9fe3-6dd69bd20cea").unwrap() + } + } + + fn embed_chronicle_tp() -> EmbeddedChronicleTp { + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + let mut buf = vec![]; + Setting { + entries: vec![Setting_Entry { + key: "chronicle.opa.policy_name".to_string(), + value: "allow_transactions".to_string(), + ..Default::default() + }] + .into(), + ..Default::default() + } + .write_to_vec(&mut buf) + .unwrap(); + let setting_id = ( + chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.policy_name"), + buf, + ); + let mut buf = vec![]; + Setting { + entries: vec![Setting_Entry { + key: "chronicle.opa.entrypoint".to_string(), + value: "allow_transactions.allowed_users".to_string(), + ..Default::default() + }] + .into(), + ..Default::default() + } + .write_to_vec(&mut buf) + .unwrap(); + + let setting_entrypoint = ( + chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.entrypoint"), + buf, + ); + + let d = env!("CARGO_MANIFEST_DIR").to_owned() + "/../../policies/bundle.tar.gz"; + let bin = std::fs::read(d).unwrap(); + + let meta = PolicyMeta { + id: "allow_transactions".to_string(), + hash: hex::encode(Sha256::digest(&bin)), + policy_address: policy_address("allow_transactions"), + }; + + EmbeddedChronicleTp::new_with_state( + vec![ + setting_id, + setting_entrypoint, + (policy_address("allow_transactions"), bin), + (policy_meta_address("allow_transactions"), serde_json::to_vec(&meta).unwrap()), + ] + .into_iter() + .collect(), + ) + .unwrap() + } + + async fn test_api<'a>() -> TestDispatch<'a> { + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + + let secrets = ChronicleSigning::new( + chronicle_secret_names(), + vec![ + (CHRONICLE_NAMESPACE.to_string(), ChronicleSecretsOptions::generate_in_memory()), + (BATCHER_NAMESPACE.to_string(), ChronicleSecretsOptions::generate_in_memory()), + ], + ) + .await + .unwrap(); + let embed_tp = embed_chronicle_tp(); + let database = TemporaryDatabase::default(); + let pool = database.connection_pool().unwrap(); + + let liveness_check_interval = None; + + let dispatch = Api::new( + pool, + embed_tp.ledger.clone(), + SameUuid, + secrets, + vec![], + Some("allow_transactions".into()), + liveness_check_interval, + ) + .await + .unwrap(); + + TestDispatch { + api: dispatch, + _db: database, // share the lifetime + _tp: embed_tp, + } + } + + // Creates a mock file containing JSON-LD of the ChronicleOperations + // that would be created by the given command, although not in any particular order. + fn test_create_agent_operations_import() -> assert_fs::NamedTempFile { + let file = assert_fs::NamedTempFile::new("import.json").unwrap(); + assert_fs::prelude::FileWriteStr::write_str( + &file, + r#" [ { "@id": "_:n1", @@ -2054,36 +1971,36 @@ mod test { } ] "#, - ) - .unwrap(); - file - } + ) + .unwrap(); + file + } - #[tokio::test] - async fn test_import_operations() { - let mut api = test_api().await; + #[tokio::test] + async fn test_import_operations() { + let mut api = test_api().await; - let file = test_create_agent_operations_import(); + let file = test_create_agent_operations_import(); - let contents = std::fs::read_to_string(file.path()).unwrap(); + let contents = std::fs::read_to_string(file.path()).unwrap(); - let json_array = serde_json::from_str::>(&contents).unwrap(); + let json_array = serde_json::from_str::>(&contents).unwrap(); - let mut operations = Vec::with_capacity(json_array.len()); - for value in json_array.into_iter() { - let op = ChronicleOperation::from_json(&value) - .await - .expect("Failed to parse imported JSON-LD to ChronicleOperation"); - operations.push(op); - } + let mut operations = Vec::with_capacity(json_array.len()); + for value in json_array.into_iter() { + let op = ChronicleOperation::from_json(&value) + .await + .expect("Failed to parse imported JSON-LD to ChronicleOperation"); + operations.push(op); + } - let namespace = NamespaceId::from_external_id( - "testns", - Uuid::parse_str("6803790d-5891-4dfa-b773-41827d2c630b").unwrap(), - ); - let identity = AuthId::chronicle(); + let namespace = NamespaceId::from_external_id( + "testns", + Uuid::parse_str("6803790d-5891-4dfa-b773-41827d2c630b").unwrap(), + ); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!(api + insta::assert_json_snapshot!(api .dispatch(ApiCommand::Import(ImportCommand { namespace: namespace.clone(), operations: operations.clone() } ), identity.clone()) .await .unwrap() @@ -2115,22 +2032,22 @@ mod test { } "###); - // Check that the operations that do not result in data changes are not submitted - insta::assert_json_snapshot!(api + // Check that the operations that do not result in data changes are not submitted + insta::assert_json_snapshot!(api .dispatch(ApiCommand::Import(ImportCommand { namespace, operations } ), identity) .await .unwrap() .unwrap() .1, @r###""null""###); - } + } - #[tokio::test] - async fn create_namespace() { - let mut api = test_api().await; + #[tokio::test] + async fn create_namespace() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!(api + insta::assert_json_snapshot!(api .dispatch(ApiCommand::NameSpace(NamespaceCommand::Create { external_id: "testns".into(), }), identity) @@ -2149,15 +2066,15 @@ mod test { "externalId": "testns" } "###); - } + } - #[tokio::test] - async fn create_agent() { - let mut api = test_api().await; + #[tokio::test] + async fn create_agent() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!(api.dispatch(ApiCommand::Agent(AgentCommand::Create { + insta::assert_json_snapshot!(api.dispatch(ApiCommand::Agent(AgentCommand::Create { external_id: "testagent".into(), namespace: "testns".into(), attributes: Attributes { @@ -2204,15 +2121,15 @@ mod test { ] } "###); - } + } - #[tokio::test] - async fn create_system_activity() { - let mut api = test_api().await; + #[tokio::test] + async fn create_system_activity() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!(api.dispatch(ApiCommand::Activity(ActivityCommand::Create { + insta::assert_json_snapshot!(api.dispatch(ApiCommand::Activity(ActivityCommand::Create { external_id: "testactivity".into(), namespace: common::prov::SYSTEM_ID.into(), attributes: Attributes { @@ -2259,15 +2176,15 @@ mod test { ] } "###); - } + } - #[tokio::test] - async fn create_activity() { - let mut api = test_api().await; + #[tokio::test] + async fn create_activity() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!(api.dispatch(ApiCommand::Activity(ActivityCommand::Create { + insta::assert_json_snapshot!(api.dispatch(ApiCommand::Activity(ActivityCommand::Create { external_id: "testactivity".into(), namespace: "testns".into(), attributes: Attributes { @@ -2314,15 +2231,15 @@ mod test { ] } "###); - } + } - #[tokio::test] - async fn start_activity() { - let mut api = test_api().await; + #[tokio::test] + async fn start_activity() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Agent(AgentCommand::Create { external_id: "testagent".into(), namespace: "testns".into(), @@ -2371,17 +2288,17 @@ mod test { } "###); - api.dispatch( - ApiCommand::Agent(AgentCommand::UseInContext { - id: AgentId::from_external_id("testagent"), - namespace: "testns".into(), - }), - identity.clone(), - ) - .await - .unwrap(); - - insta::assert_json_snapshot!( + api.dispatch( + ApiCommand::Agent(AgentCommand::UseInContext { + id: AgentId::from_external_id("testagent"), + namespace: "testns".into(), + }), + identity.clone(), + ) + .await + .unwrap(); + + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Activity(ActivityCommand::Start { id: ActivityId::from_external_id("testactivity"), namespace: "testns".into(), @@ -2430,15 +2347,15 @@ mod test { ] } "###); - } + } - #[tokio::test] - async fn contradict_attributes() { - let mut api = test_api().await; + #[tokio::test] + async fn contradict_attributes() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Agent(AgentCommand::Create { external_id: "testagent".into(), namespace: "testns".into(), @@ -2487,38 +2404,38 @@ mod test { } "###); - let res = api - .dispatch( - ApiCommand::Agent(AgentCommand::Create { - external_id: "testagent".into(), - namespace: "testns".into(), - attributes: Attributes { - typ: Some(DomaintypeId::from_external_id("test")), - attributes: [( - "test".to_owned(), - Attribute { - typ: "test".to_owned(), - value: serde_json::Value::String("test2".to_owned()), - }, - )] - .into_iter() - .collect(), - }, - }), - identity, - ) - .await; - - insta::assert_snapshot!(res.err().unwrap().to_string(), @r###"Contradiction: Contradiction { attribute value change: test Attribute { typ: "test", value: String("test2") } Attribute { typ: "test", value: String("test") } }"###); - } - - #[tokio::test] - async fn contradict_start_time() { - let mut api = test_api().await; - - let identity = AuthId::chronicle(); - - insta::assert_json_snapshot!( + let res = api + .dispatch( + ApiCommand::Agent(AgentCommand::Create { + external_id: "testagent".into(), + namespace: "testns".into(), + attributes: Attributes { + typ: Some(DomaintypeId::from_external_id("test")), + attributes: [( + "test".to_owned(), + Attribute { + typ: "test".to_owned(), + value: serde_json::Value::String("test2".to_owned()), + }, + )] + .into_iter() + .collect(), + }, + }), + identity, + ) + .await; + + insta::assert_snapshot!(res.err().unwrap().to_string(), @r###"Contradiction: Contradiction { attribute value change: test Attribute { typ: "test", value: String("test2") } Attribute { typ: "test", value: String("test") } }"###); + } + + #[tokio::test] + async fn contradict_start_time() { + let mut api = test_api().await; + + let identity = AuthId::chronicle(); + + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Agent(AgentCommand::Create { external_id: "testagent".into(), namespace: "testns".into(), @@ -2567,17 +2484,17 @@ mod test { } "###); - api.dispatch( - ApiCommand::Agent(AgentCommand::UseInContext { - id: AgentId::from_external_id("testagent"), - namespace: "testns".into(), - }), - identity.clone(), - ) - .await - .unwrap(); - - insta::assert_json_snapshot!( + api.dispatch( + ApiCommand::Agent(AgentCommand::UseInContext { + id: AgentId::from_external_id("testagent"), + namespace: "testns".into(), + }), + identity.clone(), + ) + .await + .unwrap(); + + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Activity(ActivityCommand::Start { id: ActivityId::from_external_id("testactivity"), namespace: "testns".into(), @@ -2627,29 +2544,29 @@ mod test { } "###); - // Should contradict - let res = api - .dispatch( - ApiCommand::Activity(ActivityCommand::Start { - id: ActivityId::from_external_id("testactivity"), - namespace: "testns".into(), - time: Some(Utc.with_ymd_and_hms(2018, 7, 8, 9, 10, 11).unwrap()), - agent: None, - }), - identity, - ) - .await; - - insta::assert_snapshot!(res.err().unwrap().to_string(), @"Contradiction: Contradiction { start date alteration: 2014-07-08 09:10:11 UTC 2018-07-08 09:10:11 UTC }"); - } - - #[tokio::test] - async fn contradict_end_time() { - let mut api = test_api().await; - - let identity = AuthId::chronicle(); - - insta::assert_json_snapshot!( + // Should contradict + let res = api + .dispatch( + ApiCommand::Activity(ActivityCommand::Start { + id: ActivityId::from_external_id("testactivity"), + namespace: "testns".into(), + time: Some(Utc.with_ymd_and_hms(2018, 7, 8, 9, 10, 11).unwrap()), + agent: None, + }), + identity, + ) + .await; + + insta::assert_snapshot!(res.err().unwrap().to_string(), @"Contradiction: Contradiction { start date alteration: 2014-07-08 09:10:11 UTC 2018-07-08 09:10:11 UTC }"); + } + + #[tokio::test] + async fn contradict_end_time() { + let mut api = test_api().await; + + let identity = AuthId::chronicle(); + + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Agent(AgentCommand::Create { external_id: "testagent".into(), namespace: "testns".into(), @@ -2698,17 +2615,17 @@ mod test { } "###); - api.dispatch( - ApiCommand::Agent(AgentCommand::UseInContext { - id: AgentId::from_external_id("testagent"), - namespace: "testns".into(), - }), - identity.clone(), - ) - .await - .unwrap(); - - insta::assert_json_snapshot!( + api.dispatch( + ApiCommand::Agent(AgentCommand::UseInContext { + id: AgentId::from_external_id("testagent"), + namespace: "testns".into(), + }), + identity.clone(), + ) + .await + .unwrap(); + + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Activity(ActivityCommand::End { id: ActivityId::from_external_id("testactivity"), namespace: "testns".into(), @@ -2758,29 +2675,29 @@ mod test { } "###); - // Should contradict - let res = api - .dispatch( - ApiCommand::Activity(ActivityCommand::End { - id: ActivityId::from_external_id("testactivity"), - namespace: "testns".into(), - time: Some(Utc.with_ymd_and_hms(2022, 7, 8, 9, 10, 11).unwrap()), - agent: None, - }), - identity, - ) - .await; - - insta::assert_snapshot!(res.err().unwrap().to_string(), @"Contradiction: Contradiction { end date alteration: 2018-07-08 09:10:11 UTC 2022-07-08 09:10:11 UTC }"); - } - - #[tokio::test] - async fn end_activity() { - let mut api = test_api().await; - - let identity = AuthId::chronicle(); - - insta::assert_json_snapshot!( + // Should contradict + let res = api + .dispatch( + ApiCommand::Activity(ActivityCommand::End { + id: ActivityId::from_external_id("testactivity"), + namespace: "testns".into(), + time: Some(Utc.with_ymd_and_hms(2022, 7, 8, 9, 10, 11).unwrap()), + agent: None, + }), + identity, + ) + .await; + + insta::assert_snapshot!(res.err().unwrap().to_string(), @"Contradiction: Contradiction { end date alteration: 2018-07-08 09:10:11 UTC 2022-07-08 09:10:11 UTC }"); + } + + #[tokio::test] + async fn end_activity() { + let mut api = test_api().await; + + let identity = AuthId::chronicle(); + + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Agent(AgentCommand::Create { external_id: "testagent".into(), namespace: "testns".into(), @@ -2829,17 +2746,17 @@ mod test { } "###); - api.dispatch( - ApiCommand::Agent(AgentCommand::UseInContext { - id: AgentId::from_external_id("testagent"), - namespace: "testns".into(), - }), - identity.clone(), - ) - .await - .unwrap(); - - insta::assert_json_snapshot!( + api.dispatch( + ApiCommand::Agent(AgentCommand::UseInContext { + id: AgentId::from_external_id("testagent"), + namespace: "testns".into(), + }), + identity.clone(), + ) + .await + .unwrap(); + + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Activity(ActivityCommand::Start { id: ActivityId::from_external_id("testactivity"), namespace: "testns".into(), @@ -2889,7 +2806,7 @@ mod test { } "###); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Activity(ActivityCommand::End { id: ActivityId::from_external_id("testactivity"), @@ -2925,15 +2842,15 @@ mod test { ] } "###); - } + } - #[tokio::test] - async fn activity_use() { - let mut api = test_api().await; + #[tokio::test] + async fn activity_use() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Agent(AgentCommand::Create { external_id: "testagent".into(), namespace: "testns".into(), @@ -2982,17 +2899,17 @@ mod test { } "###); - api.dispatch( - ApiCommand::Agent(AgentCommand::UseInContext { - id: AgentId::from_external_id("testagent"), - namespace: "testns".into(), - }), - identity.clone(), - ) - .await - .unwrap(); - - insta::assert_json_snapshot!( + api.dispatch( + ApiCommand::Agent(AgentCommand::UseInContext { + id: AgentId::from_external_id("testagent"), + namespace: "testns".into(), + }), + identity.clone(), + ) + .await + .unwrap(); + + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Activity(ActivityCommand::Create { external_id: "testactivity".into(), namespace: "testns".into(), @@ -3041,7 +2958,7 @@ mod test { } "###); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Activity(ActivityCommand::Use { id: EntityId::from_external_id("testentity"), namespace: "testns".into(), @@ -3089,7 +3006,7 @@ mod test { } "###); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Activity(ActivityCommand::End { id: ActivityId::from_external_id("testactivity"), namespace: "testns".into(), @@ -3146,15 +3063,15 @@ mod test { ] } "###); - } + } - #[tokio::test] - async fn activity_generate() { - let mut api = test_api().await; + #[tokio::test] + async fn activity_generate() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Activity(ActivityCommand::Create { external_id: "testactivity".into(), namespace: "testns".into(), @@ -3203,7 +3120,7 @@ mod test { } "###); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Activity(ActivityCommand::Generate { id: EntityId::from_external_id("testentity"), namespace: "testns".into(), @@ -3238,15 +3155,15 @@ mod test { ] } "###); - } + } - #[tokio::test] - async fn derive_entity_abstract() { - let mut api = test_api().await; + #[tokio::test] + async fn derive_entity_abstract() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Entity(EntityCommand::Derive { id: EntityId::from_external_id("testgeneratedentity"), namespace: "testns".into(), @@ -3290,15 +3207,15 @@ mod test { ] } "###); - } + } - #[tokio::test] - async fn derive_entity_primary_source() { - let mut api = test_api().await; + #[tokio::test] + async fn derive_entity_primary_source() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Entity(EntityCommand::Derive { id: EntityId::from_external_id("testgeneratedentity"), namespace: "testns".into(), @@ -3342,15 +3259,15 @@ mod test { ] } "###); - } + } - #[tokio::test] - async fn derive_entity_revision() { - let mut api = test_api().await; + #[tokio::test] + async fn derive_entity_revision() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Entity(EntityCommand::Derive { id: EntityId::from_external_id("testgeneratedentity"), namespace: "testns".into(), @@ -3394,15 +3311,15 @@ mod test { ] } "###); - } + } - #[tokio::test] - async fn derive_entity_quotation() { - let mut api = test_api().await; + #[tokio::test] + async fn derive_entity_quotation() { + let mut api = test_api().await; - let identity = AuthId::chronicle(); + let identity = AuthId::chronicle(); - insta::assert_json_snapshot!( + insta::assert_json_snapshot!( api.dispatch(ApiCommand::Entity(EntityCommand::Derive { id: EntityId::from_external_id("testgeneratedentity"), namespace: "testns".into(), @@ -3446,5 +3363,5 @@ mod test { ] } "###); - } + } } diff --git a/crates/api/src/opa.rs b/crates/api/src/opa.rs new file mode 100644 index 000000000..39ef0ad8f --- /dev/null +++ b/crates/api/src/opa.rs @@ -0,0 +1,157 @@ +use crate::{ + identity::{AuthId, IdentityError, OpaData}, + import::FromUrlError, +}; +use futures::lock::Mutex; +use opa::{bundle::Bundle, wasm::Opa}; + +#[cfg(not(feature = "std"))] +use parity_scale_codec::alloc::sync::Arc; +#[cfg(feature = "std")] +use std::sync::Arc; + +use rust_embed::RustEmbed; +use thiserror::Error; +use tracing::{error, instrument}; + +#[derive(Debug, Error)] +pub enum PolicyLoaderError { + #[error("Failed to read embedded OPA policies")] + EmbeddedOpaPolicies, + + #[error("Policy not found: {0}")] + MissingPolicy(String), + + #[error("OPA bundle I/O error: {0}")] + OpaBundleError(#[from] opa::bundle::Error), + + #[error("Error loading OPA policy: {0}")] + SawtoothCommunicationError(#[from] anyhow::Error), + + #[error("Error loading policy bundle from URL: {0}")] + UrlError(#[from] FromUrlError), +} + +#[async_trait::async_trait] +pub trait PolicyLoader { + /// Set address of OPA policy + fn set_address(&mut self, address: &str); + + /// Set OPA policy + fn set_rule_name(&mut self, policy: &str); + + /// Set entrypoint for OPA policy + fn set_entrypoint(&mut self, entrypoint: &str); + + fn get_address(&self) -> &str; + + fn get_rule_name(&self) -> &str; + + fn get_entrypoint(&self) -> &str; + + fn get_policy(&self) -> &[u8]; + + /// Load OPA policy from address set in `PolicyLoader` + async fn load_policy(&mut self) -> Result<(), PolicyLoaderError>; + + /// Load OPA policy from provided bytes + fn load_policy_from_bytes(&mut self, policy: &[u8]); + + /// Return a built OPA instance from the cached policy + #[instrument(level = "trace", skip(self), ret)] + fn build_opa(&self) -> Result { + Ok(Opa::new().build(self.get_policy())?) + } + + /// Load OPA policy from provided policy bundle + fn load_policy_from_bundle(&mut self, bundle: &Bundle) -> Result<(), PolicyLoaderError> { + let rule = self.get_rule_name(); + self.load_policy_from_bytes( + bundle + .wasm_policies + .iter() + .find(|p| p.entrypoint == rule) + .map(|p| p.bytes.as_ref()) + .ok_or(PolicyLoaderError::MissingPolicy(rule.to_string()))?, + ); + Ok(()) + } + + fn hash(&self) -> String; +} + +#[derive(Debug, Error)] +pub enum OpaExecutorError { + #[error("Access denied")] + AccessDenied, + + #[error("Identity error: {0}")] + IdentityError(#[from] IdentityError), + + #[error("Error loading OPA policy: {0}")] + PolicyLoaderError(#[from] PolicyLoaderError), + + #[error("Error evaluating OPA policy: {0}")] + OpaEvaluationError(#[from] anyhow::Error), +} + +#[async_trait::async_trait] +pub trait OpaExecutor { + /// Evaluate the loaded OPA instance against the provided identity and context + async fn evaluate(&mut self, id: &AuthId, context: &OpaData) -> Result<(), OpaExecutorError>; +} + +#[derive(Clone, Debug)] +pub struct ExecutorContext { + executor: Arc>, + hash: String, +} + +impl ExecutorContext { + #[instrument(skip(self), level = "trace", ret(Debug))] + pub async fn evaluate(&self, id: &AuthId, context: &OpaData) -> Result<(), OpaExecutorError> { + self.executor.lock().await.evaluate(id, context).await + } + + pub fn from_loader(loader: &L) -> Result { + Ok(Self { + executor: Arc::new(Mutex::new(WasmtimeOpaExecutor::from_loader(loader)?)), + hash: loader.hash(), + }) + } + + pub fn hash(&self) -> &str { + &self.hash + } +} + +#[derive(Debug)] +pub struct WasmtimeOpaExecutor { + opa: Opa, + entrypoint: String, +} + +impl WasmtimeOpaExecutor { + /// Build a `WasmtimeOpaExecutor` from the `PolicyLoader` provided + pub fn from_loader(loader: &L) -> Result { + Ok(Self { opa: loader.build_opa()?, entrypoint: loader.get_entrypoint().to_owned() }) + } +} + +#[async_trait::async_trait] +impl OpaExecutor for WasmtimeOpaExecutor { + #[instrument(level = "trace", skip(self))] + async fn evaluate(&mut self, id: &AuthId, context: &OpaData) -> Result<(), OpaExecutorError> { + self.opa.set_data(context)?; + let input = id.identity()?; + match self.opa.eval(&self.entrypoint, &input)? { + true => Ok(()), + false => Err(OpaExecutorError::AccessDenied), + } + } +} + +#[derive(RustEmbed)] +#[folder = "../../policies"] +#[include = "bundle.tar.gz"] +struct EmbeddedOpaPolicies; diff --git a/crates/api/src/persistence/database.rs b/crates/api/src/persistence/database.rs new file mode 100644 index 000000000..b371beadf --- /dev/null +++ b/crates/api/src/persistence/database.rs @@ -0,0 +1,65 @@ +use diesel::{r2d2::ConnectionManager, Connection, PgConnection}; +use lazy_static::lazy_static; +use r2d2::Pool; +use std::{fmt::Display, time::Duration}; +use testcontainers::{clients::Cli, images::postgres::Postgres, Container}; + +lazy_static! { + static ref CLIENT: Cli = Cli::default(); +} + +pub struct TemporaryDatabase<'a> { + db_uris: Vec, + _container: Container<'a, Postgres>, +} + +impl<'a> TemporaryDatabase<'a> { + pub fn connection_pool(&self) -> Result>, r2d2::Error> { + let db_uri = self + .db_uris + .iter() + .find(|db_uri| PgConnection::establish(db_uri).is_ok()) + .expect("cannot establish connection"); + Pool::builder().build(ConnectionManager::::new(db_uri)) + } +} + +impl<'a> Default for TemporaryDatabase<'a> { + fn default() -> Self { + let container = CLIENT.run(Postgres::default()); + const PORT: u16 = 5432; + Self { + db_uris: vec![ + format!("postgresql://postgres@127.0.0.1:{}/", container.get_host_port_ipv4(PORT)), + format!("postgresql://postgres@{}:{}/", container.get_bridge_ip_address(), PORT), + ], + _container: container, + } + } +} + +#[async_trait::async_trait] +pub trait DatabaseConnector { + async fn try_connect(&self) -> Result<(X, Pool>), E>; + fn should_retry(&self, error: &E) -> bool; +} + +pub async fn get_connection_with_retry( + connector: impl DatabaseConnector, +) -> Result<(X, Pool>), E> { + let mut i = 1; + let mut j = 1; + loop { + let connection = connector.try_connect().await; + if let Err(source) = &connection { + tracing::warn!("database connection failed: {source}"); + if i < 20 && connector.should_retry(source) { + tracing::info!("waiting to retry database connection..."); + std::thread::sleep(Duration::from_secs(i)); + (i, j) = (i + j, i); + continue; + } + } + return connection; + } +} diff --git a/crates/api/src/persistence/mod.rs b/crates/api/src/persistence/mod.rs index fb9c232c6..0e363446d 100644 --- a/crates/api/src/persistence/mod.rs +++ b/crates/api/src/persistence/mod.rs @@ -4,20 +4,20 @@ use async_stl_client::ledger::{BlockId, BlockIdError}; use chrono::{TimeZone, Utc}; use common::{ - attributes::Attribute, - prov::{ - operations::DerivationType, Activity, ActivityId, Agent, AgentId, Association, Attribution, - ChronicleTransactionId, ChronicleTransactionIdError, Delegation, Derivation, DomaintypeId, - Entity, EntityId, ExternalId, ExternalIdPart, Generation, Identity, IdentityId, Namespace, - NamespaceId, ProvModel, PublicKeyPart, Role, Usage, - }, + attributes::Attribute, + prov::{ + operations::DerivationType, Activity, ActivityId, Agent, AgentId, Association, Attribution, + ChronicleTransactionId, ChronicleTransactionIdError, Delegation, Derivation, DomaintypeId, + Entity, EntityId, ExternalId, ExternalIdPart, Generation, Identity, IdentityId, Namespace, + NamespaceId, ProvModel, PublicKeyPart, Role, Usage, + }, }; use derivative::*; use diesel::{ - prelude::*, - r2d2::{ConnectionManager, Pool, PooledConnection}, - PgConnection, + prelude::*, + r2d2::{ConnectionManager, Pool, PooledConnection}, + PgConnection, }; use diesel_migrations::{embed_migrations, EmbeddedMigrations}; use thiserror::Error; @@ -30,154 +30,143 @@ pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); #[derive(Error, Debug)] pub enum StoreError { - #[error("Database operation failed: {0}")] - Db(#[from] diesel::result::Error), + #[error("Database operation failed: {0}")] + Db(#[from] diesel::result::Error), - #[error("Database connection failed (maybe check PGPASSWORD): {0}")] - DbConnection(#[from] diesel::ConnectionError), + #[error("Database connection failed (maybe check PGPASSWORD): {0}")] + DbConnection(#[from] diesel::ConnectionError), - #[error("Database migration failed: {0}")] - DbMigration(#[from] Box), + #[error("Database migration failed: {0}")] + DbMigration(#[from] Box), - #[error("Connection pool error: {0}")] - DbPool(#[from] r2d2::Error), + #[error("Connection pool error: {0}")] + DbPool(#[from] r2d2::Error), - #[error("Infallible")] - Infallible(#[from] std::convert::Infallible), + #[error("Infallible")] + Infallible(#[from] std::convert::Infallible), - #[error( - "Integer returned from database was an unrecognized 'DerivationType' enum variant: {0}" - )] - InvalidDerivationTypeRecord(i32), + #[error( + "Integer returned from database was an unrecognized 'DerivationType' enum variant: {0}" + )] + InvalidDerivationTypeRecord(i32), - #[error("Could not find namespace")] - InvalidNamespace, + #[error("Could not find namespace")] + InvalidNamespace, - #[error("Unreadable Attribute: {0}")] - Json(#[from] serde_json::Error), + #[error("Unreadable Attribute: {0}")] + Json(#[from] serde_json::Error), - #[error("Parse blockid: {0}")] - ParseBlockId(#[from] BlockIdError), + #[error("Parse blockid: {0}")] + ParseBlockId(#[from] BlockIdError), - #[error("Invalid transaction ID: {0}")] - TransactionId(#[from] ChronicleTransactionIdError), + #[error("Invalid transaction ID: {0}")] + TransactionId(#[from] ChronicleTransactionIdError), - #[error("Could not locate record in store")] - RecordNotFound, + #[error("Could not locate record in store")] + RecordNotFound, - #[error("Invalid UUID: {0}")] - Uuid(#[from] uuid::Error), + #[error("Invalid UUID: {0}")] + Uuid(#[from] uuid::Error), } #[derive(Debug)] pub struct ConnectionOptions { - pub enable_wal: bool, - pub enable_foreign_keys: bool, - pub busy_timeout: Option, + pub enable_wal: bool, + pub enable_foreign_keys: bool, + pub busy_timeout: Option, } #[instrument] fn sleeper(attempts: i32) -> bool { - warn!(attempts, "SQLITE_BUSY, retrying"); - std::thread::sleep(std::time::Duration::from_millis(250)); - true + warn!(attempts, "SQLITE_BUSY, retrying"); + std::thread::sleep(std::time::Duration::from_millis(250)); + true } #[derive(Derivative)] #[derivative(Debug, Clone)] pub struct Store { - #[derivative(Debug = "ignore")] - pool: Pool>, + #[derivative(Debug = "ignore")] + pool: Pool>, } impl Store { - #[instrument(name = "Bind namespace", skip(self))] - pub(crate) fn namespace_binding( - &self, - external_id: &str, - uuid: Uuid, - ) -> Result<(), StoreError> { - use schema::namespace::dsl; - - let uuid = uuid.to_string(); - self.connection()?.build_transaction().run(|conn| { - diesel::insert_into(dsl::namespace) - .values((dsl::external_id.eq(external_id), dsl::uuid.eq(&uuid))) - .on_conflict(dsl::external_id) - .do_update() - .set(dsl::uuid.eq(&uuid)) - .execute(conn) - })?; - - Ok(()) - } - - /// Fetch the activity record for the IRI - fn activity_by_activity_external_id_and_namespace( - &self, - connection: &mut PgConnection, - external_id: &ExternalId, - namespaceid: &NamespaceId, - ) -> Result { - let (_namespaceid, nsid) = - self.namespace_by_external_id(connection, namespaceid.external_id_part())?; - use schema::activity::dsl; - - Ok(schema::activity::table - .filter( - dsl::external_id - .eq(external_id) - .and(dsl::namespace_id.eq(nsid)), - ) - .first::(connection)?) - } - - /// Fetch the entity record for the IRI - fn entity_by_entity_external_id_and_namespace( - &self, - connection: &mut PgConnection, - external_id: &ExternalId, - namespace_id: &NamespaceId, - ) -> Result { - let (_, ns_id) = - self.namespace_by_external_id(connection, namespace_id.external_id_part())?; - use schema::entity::dsl; - - Ok(schema::entity::table - .filter( - dsl::external_id - .eq(external_id) - .and(dsl::namespace_id.eq(ns_id)), - ) - .first::(connection)?) - } - - /// Fetch the agent record for the IRI - pub(crate) fn agent_by_agent_external_id_and_namespace( - &self, - connection: &mut PgConnection, - external_id: &ExternalId, - namespaceid: &NamespaceId, - ) -> Result { - let (_namespaceid, nsid) = - self.namespace_by_external_id(connection, namespaceid.external_id_part())?; - use schema::agent::dsl; - - Ok(schema::agent::table - .filter( - dsl::external_id - .eq(external_id) - .and(dsl::namespace_id.eq(nsid)), - ) - .first::(connection)?) - } - - /// Apply an activity to persistent storage, name + namespace are a key, so we update times + domaintype on conflict - #[instrument(level = "trace", skip(self, connection), ret(Debug))] - fn apply_activity( - &self, - connection: &mut PgConnection, - Activity { + #[instrument(name = "Bind namespace", skip(self))] + pub(crate) fn namespace_binding( + &self, + external_id: &str, + uuid: Uuid, + ) -> Result<(), StoreError> { + use schema::namespace::dsl; + + let uuid = uuid.to_string(); + self.connection()?.build_transaction().run(|conn| { + diesel::insert_into(dsl::namespace) + .values((dsl::external_id.eq(external_id), dsl::uuid.eq(&uuid))) + .on_conflict(dsl::external_id) + .do_update() + .set(dsl::uuid.eq(&uuid)) + .execute(conn) + })?; + + Ok(()) + } + + /// Fetch the activity record for the IRI + fn activity_by_activity_external_id_and_namespace( + &self, + connection: &mut PgConnection, + external_id: &ExternalId, + namespaceid: &NamespaceId, + ) -> Result { + let (_namespaceid, nsid) = + self.namespace_by_external_id(connection, namespaceid.external_id_part())?; + use schema::activity::dsl; + + Ok(schema::activity::table + .filter(dsl::external_id.eq(external_id).and(dsl::namespace_id.eq(nsid))) + .first::(connection)?) + } + + /// Fetch the entity record for the IRI + fn entity_by_entity_external_id_and_namespace( + &self, + connection: &mut PgConnection, + external_id: &ExternalId, + namespace_id: &NamespaceId, + ) -> Result { + let (_, ns_id) = + self.namespace_by_external_id(connection, namespace_id.external_id_part())?; + use schema::entity::dsl; + + Ok(schema::entity::table + .filter(dsl::external_id.eq(external_id).and(dsl::namespace_id.eq(ns_id))) + .first::(connection)?) + } + + /// Fetch the agent record for the IRI + pub(crate) fn agent_by_agent_external_id_and_namespace( + &self, + connection: &mut PgConnection, + external_id: &ExternalId, + namespaceid: &NamespaceId, + ) -> Result { + let (_namespaceid, nsid) = + self.namespace_by_external_id(connection, namespaceid.external_id_part())?; + use schema::agent::dsl; + + Ok(schema::agent::table + .filter(dsl::external_id.eq(external_id).and(dsl::namespace_id.eq(nsid))) + .first::(connection)?) + } + + /// Apply an activity to persistent storage, name + namespace are a key, so we update times + + /// domaintype on conflict + #[instrument(level = "trace", skip(self, connection), ret(Debug))] + fn apply_activity( + &self, + connection: &mut PgConnection, + Activity { ref external_id, namespaceid, started, @@ -186,1269 +175,1190 @@ impl Store { attributes, .. }: &Activity, - ns: &BTreeMap, - ) -> Result<(), StoreError> { - use schema::activity as dsl; - let _namespace = ns.get(namespaceid).ok_or(StoreError::InvalidNamespace {})?; - let (_, nsid) = - self.namespace_by_external_id(connection, namespaceid.external_id_part())?; - - let existing = self - .activity_by_activity_external_id_and_namespace(connection, external_id, namespaceid) - .ok(); - - let resolved_domain_type = domaintypeid - .as_ref() - .map(|x| x.external_id_part().clone()) - .or_else(|| { - existing - .as_ref() - .and_then(|x| x.domaintype.as_ref().map(ExternalId::from)) - }); - - let resolved_started = started - .map(|x| x.naive_utc()) - .or_else(|| existing.as_ref().and_then(|x| x.started)); - - let resolved_ended = ended - .map(|x| x.naive_utc()) - .or_else(|| existing.as_ref().and_then(|x| x.ended)); - - diesel::insert_into(schema::activity::table) - .values(( - dsl::external_id.eq(external_id), - dsl::namespace_id.eq(nsid), - dsl::started.eq(started.map(|t| t.naive_utc())), - dsl::ended.eq(ended.map(|t| t.naive_utc())), - dsl::domaintype.eq(domaintypeid.as_ref().map(|x| x.external_id_part())), - )) - .on_conflict((dsl::external_id, dsl::namespace_id)) - .do_update() - .set(( - dsl::domaintype.eq(resolved_domain_type), - dsl::started.eq(resolved_started), - dsl::ended.eq(resolved_ended), - )) - .execute(connection)?; - - let query::Activity { id, .. } = self.activity_by_activity_external_id_and_namespace( - connection, - external_id, - namespaceid, - )?; - - diesel::insert_into(schema::activity_attribute::table) - .values( - attributes - .iter() - .map( - |(_, Attribute { typ, value, .. })| query::ActivityAttribute { - activity_id: id, - typename: typ.to_owned(), - value: value.to_string(), - }, - ) - .collect::>(), - ) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - /// Apply an agent to persistent storage, external_id + namespace are a key, so we update publickey + domaintype on conflict - /// current is a special case, only relevant to local CLI context. A possibly improved design would be to store this in another table given its scope - #[instrument(level = "trace", skip(self, connection), ret(Debug))] - fn apply_agent( - &self, - connection: &mut PgConnection, - Agent { - ref external_id, - namespaceid, - domaintypeid, - attributes, - .. - }: &Agent, - ns: &BTreeMap, - ) -> Result<(), StoreError> { - use schema::agent::dsl; - let _namespace = ns.get(namespaceid).ok_or(StoreError::InvalidNamespace {})?; - let (_, nsid) = - self.namespace_by_external_id(connection, namespaceid.external_id_part())?; - - let existing = self - .agent_by_agent_external_id_and_namespace(connection, external_id, namespaceid) - .ok(); - - let resolved_domain_type = domaintypeid - .as_ref() - .map(|x| x.external_id_part().clone()) - .or_else(|| { - existing - .as_ref() - .and_then(|x| x.domaintype.as_ref().map(ExternalId::from)) - }); - - diesel::insert_into(schema::agent::table) - .values(( - dsl::external_id.eq(external_id), - dsl::namespace_id.eq(nsid), - dsl::current.eq(0), - dsl::domaintype.eq(domaintypeid.as_ref().map(|x| x.external_id_part())), - )) - .on_conflict((dsl::namespace_id, dsl::external_id)) - .do_update() - .set(dsl::domaintype.eq(resolved_domain_type)) - .execute(connection)?; - - let query::Agent { id, .. } = - self.agent_by_agent_external_id_and_namespace(connection, external_id, namespaceid)?; - - diesel::insert_into(schema::agent_attribute::table) - .values( - attributes - .iter() - .map(|(_, Attribute { typ, value, .. })| query::AgentAttribute { - agent_id: id, - typename: typ.to_owned(), - value: value.to_string(), - }) - .collect::>(), - ) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - #[instrument(level = "trace", skip(self, connection), ret(Debug))] - fn apply_entity( - &self, - connection: &mut PgConnection, - Entity { - namespaceid, - id, - external_id, - domaintypeid, - attributes, - }: &Entity, - ns: &BTreeMap, - ) -> Result<(), StoreError> { - use schema::entity::dsl; - let _namespace = ns.get(namespaceid).ok_or(StoreError::InvalidNamespace {})?; - let (_, nsid) = - self.namespace_by_external_id(connection, namespaceid.external_id_part())?; - - let existing = self - .entity_by_entity_external_id_and_namespace(connection, external_id, namespaceid) - .ok(); - - let resolved_domain_type = domaintypeid - .as_ref() - .map(|x| x.external_id_part().clone()) - .or_else(|| { - existing - .as_ref() - .and_then(|x| x.domaintype.as_ref().map(ExternalId::from)) - }); - - diesel::insert_into(schema::entity::table) - .values(( - dsl::external_id.eq(&external_id), - dsl::namespace_id.eq(nsid), - dsl::domaintype.eq(domaintypeid.as_ref().map(|x| x.external_id_part())), - )) - .on_conflict((dsl::namespace_id, dsl::external_id)) - .do_update() - .set(dsl::domaintype.eq(resolved_domain_type)) - .execute(connection)?; - - let query::Entity { id, .. } = - self.entity_by_entity_external_id_and_namespace(connection, external_id, namespaceid)?; - - diesel::insert_into(schema::entity_attribute::table) - .values( - attributes - .iter() - .map(|(_, Attribute { typ, value, .. })| query::EntityAttribute { - entity_id: id, - typename: typ.to_owned(), - value: value.to_string(), - }) - .collect::>(), - ) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - #[instrument(level = "trace", skip(self, connection), ret(Debug))] - fn apply_has_identity( - &self, - connection: &mut PgConnection, - model: &ProvModel, - namespaceid: &NamespaceId, - agent: &AgentId, - identity: &IdentityId, - ) -> Result<(), StoreError> { - let (_, nsid) = - self.namespace_by_external_id(connection, namespaceid.external_id_part())?; - let identity = self.identity_by(connection, namespaceid, identity)?; - use schema::agent::dsl; - - diesel::update(schema::agent::table) - .filter( - dsl::external_id - .eq(agent.external_id_part()) - .and(dsl::namespace_id.eq(nsid)), - ) - .set(dsl::identity_id.eq(identity.id)) - .execute(connection)?; - - Ok(()) - } - - #[instrument(level = "trace", skip(self, connection), ret(Debug))] - fn apply_had_identity( - &self, - connection: &mut PgConnection, - model: &ProvModel, - namespaceid: &NamespaceId, - agent: &AgentId, - identity: &IdentityId, - ) -> Result<(), StoreError> { - let identity = self.identity_by(connection, namespaceid, identity)?; - let agent = self.agent_by_agent_external_id_and_namespace( - connection, - agent.external_id_part(), - namespaceid, - )?; - use schema::hadidentity::dsl; - - diesel::insert_into(schema::hadidentity::table) - .values((dsl::agent_id.eq(agent.id), dsl::identity_id.eq(identity.id))) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - #[instrument(level = "trace", skip(self, connection), ret(Debug))] - fn apply_identity( - &self, - connection: &mut PgConnection, - Identity { - id, - namespaceid, - public_key, - .. - }: &Identity, - ns: &BTreeMap, - ) -> Result<(), StoreError> { - use schema::identity::dsl; - let _namespace = ns.get(namespaceid).ok_or(StoreError::InvalidNamespace {})?; - let (_, nsid) = - self.namespace_by_external_id(connection, namespaceid.external_id_part())?; - - diesel::insert_into(schema::identity::table) - .values((dsl::namespace_id.eq(nsid), dsl::public_key.eq(public_key))) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - fn apply_model( - &self, - connection: &mut PgConnection, - model: &ProvModel, - ) -> Result<(), StoreError> { - for (_, ns) in model.namespaces.iter() { - self.apply_namespace(connection, ns)? - } - for (_, agent) in model.agents.iter() { - self.apply_agent(connection, agent, &model.namespaces)? - } - for (_, activity) in model.activities.iter() { - self.apply_activity(connection, activity, &model.namespaces)? - } - for (_, entity) in model.entities.iter() { - self.apply_entity(connection, entity, &model.namespaces)? - } - for (_, identity) in model.identities.iter() { - self.apply_identity(connection, identity, &model.namespaces)? - } - - for ((namespaceid, agent_id), (_, identity_id)) in model.has_identity.iter() { - self.apply_has_identity(connection, model, namespaceid, agent_id, identity_id)?; - } - - for ((namespaceid, agent_id), identity_id) in model.had_identity.iter() { - for (_, identity_id) in identity_id { - self.apply_had_identity(connection, model, namespaceid, agent_id, identity_id)?; - } - } - - for ((namespaceid, _), association) in model.association.iter() { - for association in association.iter() { - self.apply_was_associated_with(connection, namespaceid, association)?; - } - } - - for ((namespaceid, _), usage) in model.usage.iter() { - for usage in usage.iter() { - self.apply_used(connection, namespaceid, usage)?; - } - } - - for ((namespaceid, activity_id), was_informed_by) in model.was_informed_by.iter() { - for (_, informing_activity_id) in was_informed_by.iter() { - self.apply_was_informed_by( - connection, - namespaceid, - activity_id, - informing_activity_id, - )?; - } - } - - for ((namespaceid, _), generation) in model.generation.iter() { - for generation in generation.iter() { - self.apply_was_generated_by(connection, namespaceid, generation)?; - } - } - - for ((namespaceid, _), derivation) in model.derivation.iter() { - for derivation in derivation.iter() { - self.apply_derivation(connection, namespaceid, derivation)?; - } - } - - for ((namespaceid, _), delegation) in model.delegation.iter() { - for delegation in delegation.iter() { - self.apply_delegation(connection, namespaceid, delegation)?; - } - } - - for ((namespace_id, _), attribution) in model.attribution.iter() { - for attribution in attribution.iter() { - self.apply_was_attributed_to(connection, namespace_id, attribution)?; - } - } - - Ok(()) - } - - #[instrument(level = "trace", skip(self, connection), ret(Debug))] - fn apply_namespace( - &self, - connection: &mut PgConnection, - Namespace { - ref external_id, - ref uuid, - .. - }: &Namespace, - ) -> Result<(), StoreError> { - use schema::namespace::dsl; - diesel::insert_into(schema::namespace::table) - .values(( - dsl::external_id.eq(external_id), - dsl::uuid.eq(uuid.to_string()), - )) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - pub(crate) fn apply_prov(&self, prov: &ProvModel) -> Result<(), StoreError> { - self.connection()? - .build_transaction() - .run(|connection| self.apply_model(connection, prov))?; - - Ok(()) - } - - #[instrument(skip(connection))] - fn apply_used( - &self, - connection: &mut PgConnection, - namespace: &NamespaceId, - usage: &Usage, - ) -> Result<(), StoreError> { - let storedactivity = self.activity_by_activity_external_id_and_namespace( - connection, - usage.activity_id.external_id_part(), - namespace, - )?; - - let storedentity = self.entity_by_entity_external_id_and_namespace( - connection, - usage.entity_id.external_id_part(), - namespace, - )?; - - use schema::usage::dsl as link; - diesel::insert_into(schema::usage::table) - .values(( - &link::activity_id.eq(storedactivity.id), - &link::entity_id.eq(storedentity.id), - )) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - #[instrument(skip(connection))] - fn apply_was_informed_by( - &self, - connection: &mut PgConnection, - namespace: &NamespaceId, - activity_id: &ActivityId, - informing_activity_id: &ActivityId, - ) -> Result<(), StoreError> { - let storedactivity = self.activity_by_activity_external_id_and_namespace( - connection, - activity_id.external_id_part(), - namespace, - )?; - - let storedinformingactivity = self.activity_by_activity_external_id_and_namespace( - connection, - informing_activity_id.external_id_part(), - namespace, - )?; - - use schema::wasinformedby::dsl as link; - diesel::insert_into(schema::wasinformedby::table) - .values(( - &link::activity_id.eq(storedactivity.id), - &link::informing_activity_id.eq(storedinformingactivity.id), - )) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - #[instrument(skip(self, connection))] - fn apply_was_associated_with( - &self, - connection: &mut PgConnection, - namespaceid: &common::prov::NamespaceId, - association: &Association, - ) -> Result<(), StoreError> { - let storedactivity = self.activity_by_activity_external_id_and_namespace( - connection, - association.activity_id.external_id_part(), - namespaceid, - )?; - - let storedagent = self.agent_by_agent_external_id_and_namespace( - connection, - association.agent_id.external_id_part(), - namespaceid, - )?; - - use schema::association::dsl as asoc; - let no_role = common::prov::Role("".to_string()); - diesel::insert_into(schema::association::table) - .values(( - &asoc::activity_id.eq(storedactivity.id), - &asoc::agent_id.eq(storedagent.id), - &asoc::role.eq(association.role.as_ref().unwrap_or(&no_role)), - )) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - #[instrument(skip(self, connection, namespace))] - fn apply_delegation( - &self, - connection: &mut PgConnection, - namespace: &common::prov::NamespaceId, - delegation: &Delegation, - ) -> Result<(), StoreError> { - let responsible = self.agent_by_agent_external_id_and_namespace( - connection, - delegation.responsible_id.external_id_part(), - namespace, - )?; - - let delegate = self.agent_by_agent_external_id_and_namespace( - connection, - delegation.delegate_id.external_id_part(), - namespace, - )?; - - let activity = { - if let Some(ref activity_id) = delegation.activity_id { - Some( - self.activity_by_activity_external_id_and_namespace( - connection, - activity_id.external_id_part(), - namespace, - )? - .id, - ) - } else { - None - } - }; - - use schema::delegation::dsl as link; - let no_role = common::prov::Role("".to_string()); - diesel::insert_into(schema::delegation::table) - .values(( - &link::responsible_id.eq(responsible.id), - &link::delegate_id.eq(delegate.id), - &link::activity_id.eq(activity.unwrap_or(-1)), - &link::role.eq(delegation.role.as_ref().unwrap_or(&no_role)), - )) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - #[instrument(skip(self, connection, namespace))] - fn apply_derivation( - &self, - connection: &mut PgConnection, - namespace: &common::prov::NamespaceId, - derivation: &Derivation, - ) -> Result<(), StoreError> { - let stored_generated = self.entity_by_entity_external_id_and_namespace( - connection, - derivation.generated_id.external_id_part(), - namespace, - )?; - - let stored_used = self.entity_by_entity_external_id_and_namespace( - connection, - derivation.used_id.external_id_part(), - namespace, - )?; - - let stored_activity = derivation - .activity_id - .as_ref() - .map(|activity_id| { - self.activity_by_activity_external_id_and_namespace( - connection, - activity_id.external_id_part(), - namespace, - ) - }) - .transpose()?; - - use schema::derivation::dsl as link; - diesel::insert_into(schema::derivation::table) - .values(( - &link::used_entity_id.eq(stored_used.id), - &link::generated_entity_id.eq(stored_generated.id), - &link::typ.eq(derivation.typ), - &link::activity_id.eq(stored_activity.map_or(-1, |activity| activity.id)), - )) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - #[instrument(skip(connection))] - fn apply_was_generated_by( - &self, - connection: &mut PgConnection, - namespace: &common::prov::NamespaceId, - generation: &Generation, - ) -> Result<(), StoreError> { - let storedactivity = self.activity_by_activity_external_id_and_namespace( - connection, - generation.activity_id.external_id_part(), - namespace, - )?; - - let storedentity = self.entity_by_entity_external_id_and_namespace( - connection, - generation.generated_id.external_id_part(), - namespace, - )?; - - use schema::generation::dsl as link; - diesel::insert_into(schema::generation::table) - .values(( - &link::activity_id.eq(storedactivity.id), - &link::generated_entity_id.eq(storedentity.id), - )) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - #[instrument(skip(self, connection))] - fn apply_was_attributed_to( - &self, - connection: &mut PgConnection, - namespace_id: &common::prov::NamespaceId, - attribution: &Attribution, - ) -> Result<(), StoreError> { - let stored_entity = self.entity_by_entity_external_id_and_namespace( - connection, - attribution.entity_id.external_id_part(), - namespace_id, - )?; - - let stored_agent = self.agent_by_agent_external_id_and_namespace( - connection, - attribution.agent_id.external_id_part(), - namespace_id, - )?; - - use schema::attribution::dsl as attr; - let no_role = common::prov::Role("".to_string()); - diesel::insert_into(schema::attribution::table) - .values(( - &attr::entity_id.eq(stored_entity.id), - &attr::agent_id.eq(stored_agent.id), - &attr::role.eq(attribution.role.as_ref().unwrap_or(&no_role)), - )) - .on_conflict_do_nothing() - .execute(connection)?; - - Ok(()) - } - - pub(crate) fn connection( - &self, - ) -> Result>, StoreError> { - Ok(self.pool.get()?) - } - - #[instrument(skip(connection))] - pub(crate) fn get_current_agent( - &self, - connection: &mut PgConnection, - ) -> Result { - use schema::agent::dsl; - Ok(schema::agent::table - .filter(dsl::current.ne(0)) - .first::(connection)?) - } - - /// Get the last fully synchronized offset - #[instrument] - pub(crate) fn get_last_block_id(&self) -> Result, StoreError> { - use schema::ledgersync::dsl; - self.connection()?.build_transaction().run(|connection| { - let block_id_and_tx = schema::ledgersync::table - .order_by(dsl::sync_time) - .select((dsl::bc_offset, dsl::tx_id)) - .first::<(Option, String)>(connection) - .map_err(StoreError::from)?; - - if let Some(block_id) = block_id_and_tx.0 { - Ok(Some(BlockId::try_from(block_id)?)) - } else { - Ok(None) - } - }) - } - - #[instrument(skip(connection))] - pub(crate) fn namespace_by_external_id( - &self, - connection: &mut PgConnection, - namespace: &ExternalId, - ) -> Result<(NamespaceId, i32), StoreError> { - use self::schema::namespace::dsl; - - let ns = dsl::namespace - .filter(dsl::external_id.eq(namespace)) - .select((dsl::id, dsl::external_id, dsl::uuid)) - .first::<(i32, String, String)>(connection) - .optional()? - .ok_or(StoreError::RecordNotFound {})?; - - Ok(( - NamespaceId::from_external_id(ns.1, Uuid::from_str(&ns.2)?), - ns.0, - )) - } - - #[instrument(skip(connection))] - pub(crate) fn identity_by( - &self, - connection: &mut PgConnection, - namespaceid: &NamespaceId, - identity: &IdentityId, - ) -> Result { - use self::schema::identity::dsl; - let (_, nsid) = - self.namespace_by_external_id(connection, namespaceid.external_id_part())?; - let public_key = identity.public_key_part(); - - Ok(dsl::identity - .filter( - dsl::public_key - .eq(public_key) - .and(dsl::namespace_id.eq(nsid)), - ) - .first::(connection)?) - } - - #[instrument] - pub(crate) fn new(pool: Pool>) -> Result { - Ok(Store { pool }) - } - - pub(crate) fn prov_model_for_agent( - &self, - agent: query::Agent, - namespaceid: &NamespaceId, - model: &mut ProvModel, - connection: &mut PgConnection, - ) -> Result<(), StoreError> { - debug!(?agent, "Map agent to prov"); - - let attributes = schema::agent_attribute::table - .filter(schema::agent_attribute::agent_id.eq(&agent.id)) - .load::(connection)?; - - let agentid: AgentId = AgentId::from_external_id(&agent.external_id); - model.agents.insert( - (namespaceid.clone(), agentid.clone()), - Agent { - id: agentid, - namespaceid: namespaceid.clone(), - external_id: ExternalId::from(&agent.external_id), - domaintypeid: agent.domaintype.map(DomaintypeId::from_external_id), - attributes: attributes - .into_iter() - .map(|attr| { - serde_json::from_str(&attr.value).map(|value| { - ( - attr.typename.clone(), - Attribute { - typ: attr.typename, - value, - }, - ) - }) - }) - .collect::, _>>()?, - }, - ); - - for (responsible, activity, role) in schema::delegation::table - .filter(schema::delegation::delegate_id.eq(agent.id)) - .inner_join( - schema::agent::table.on(schema::delegation::responsible_id.eq(schema::agent::id)), - ) - .inner_join( - schema::activity::table - .on(schema::delegation::activity_id.eq(schema::activity::id)), - ) - .order(schema::agent::external_id) - .select(( - schema::agent::external_id, - schema::activity::external_id, - schema::delegation::role, - )) - .load::<(String, String, String)>(connection)? - { - model.qualified_delegation( - namespaceid, - &AgentId::from_external_id(responsible), - &AgentId::from_external_id(&agent.external_id), - { - if activity.contains("hidden entry for Option None") { - None - } else { - Some(ActivityId::from_external_id(activity)) - } - }, - { - if role.is_empty() { - None - } else { - Some(Role(role)) - } - }, - ); - } - - Ok(()) - } - - pub(crate) fn prov_model_for_activity( - &self, - activity: query::Activity, - namespaceid: &NamespaceId, - model: &mut ProvModel, - connection: &mut PgConnection, - ) -> Result<(), StoreError> { - debug!(?activity, "Map activity to prov"); - - let attributes = schema::activity_attribute::table - .filter(schema::activity_attribute::activity_id.eq(&activity.id)) - .load::(connection)?; - - let id: ActivityId = ActivityId::from_external_id(&activity.external_id); - model.activities.insert( - (namespaceid.clone(), id.clone()), - Activity { - id: id.clone(), - namespaceid: namespaceid.clone(), - external_id: activity.external_id.into(), - started: activity.started.map(|x| Utc.from_utc_datetime(&x)), - ended: activity.ended.map(|x| Utc.from_utc_datetime(&x)), - domaintypeid: activity.domaintype.map(DomaintypeId::from_external_id), - attributes: attributes - .into_iter() - .map(|attr| { - serde_json::from_str(&attr.value).map(|value| { - ( - attr.typename.clone(), - Attribute { - typ: attr.typename, - value, - }, - ) - }) - }) - .collect::, _>>()?, - }, - ); - - for generation in schema::generation::table - .filter(schema::generation::activity_id.eq(activity.id)) - .order(schema::generation::activity_id.asc()) - .inner_join(schema::entity::table) - .select(schema::entity::external_id) - .load::(connection)? - { - model.was_generated_by( - namespaceid.clone(), - &EntityId::from_external_id(generation), - &id, - ); - } - - for used in schema::usage::table - .filter(schema::usage::activity_id.eq(activity.id)) - .order(schema::usage::activity_id.asc()) - .inner_join(schema::entity::table) - .select(schema::entity::external_id) - .load::(connection)? - { - model.used(namespaceid.clone(), &id, &EntityId::from_external_id(used)); - } - - for wasinformedby in schema::wasinformedby::table - .filter(schema::wasinformedby::activity_id.eq(activity.id)) - .inner_join( - schema::activity::table - .on(schema::wasinformedby::informing_activity_id.eq(schema::activity::id)), - ) - .select(schema::activity::external_id) - .load::(connection)? - { - model.was_informed_by( - namespaceid.clone(), - &id, - &ActivityId::from_external_id(wasinformedby), - ); - } - - for (agent, role) in schema::association::table - .filter(schema::association::activity_id.eq(activity.id)) - .order(schema::association::activity_id.asc()) - .inner_join(schema::agent::table) - .select((schema::agent::external_id, schema::association::role)) - .load::<(String, String)>(connection)? - { - model.qualified_association(namespaceid, &id, &AgentId::from_external_id(agent), { - if role.is_empty() { - None - } else { - Some(Role(role)) - } - }); - } - - Ok(()) - } - - pub(crate) fn prov_model_for_entity( - &self, - entity: query::Entity, - namespace_id: &NamespaceId, - model: &mut ProvModel, - connection: &mut PgConnection, - ) -> Result<(), StoreError> { - debug!(?entity, "Map entity to prov"); - - let query::Entity { - id, - namespace_id: _, - domaintype, - external_id, - } = entity; - - let entity_id = EntityId::from_external_id(&external_id); - - for (agent, role) in schema::attribution::table - .filter(schema::attribution::entity_id.eq(&id)) - .order(schema::attribution::entity_id.asc()) - .inner_join(schema::agent::table) - .select((schema::agent::external_id, schema::attribution::role)) - .load::<(String, String)>(connection)? - { - model.qualified_attribution( - namespace_id, - &entity_id, - &AgentId::from_external_id(agent), - { - if role.is_empty() { - None - } else { - Some(Role(role)) - } - }, - ); - } - - let attributes = schema::entity_attribute::table - .filter(schema::entity_attribute::entity_id.eq(&id)) - .load::(connection)?; - - model.entities.insert( - (namespace_id.clone(), entity_id.clone()), - Entity { - id: entity_id.clone(), - namespaceid: namespace_id.clone(), - external_id: external_id.into(), - domaintypeid: domaintype.map(DomaintypeId::from_external_id), - attributes: attributes - .into_iter() - .map(|attr| { - serde_json::from_str(&attr.value).map(|value| { - ( - attr.typename.clone(), - Attribute { - typ: attr.typename, - value, - }, - ) - }) - }) - .collect::, _>>()?, - }, - ); - - for (activity_id, activity_external_id, used_entity_id, typ) in schema::derivation::table - .filter(schema::derivation::generated_entity_id.eq(&id)) - .order(schema::derivation::generated_entity_id.asc()) - .inner_join( - schema::activity::table - .on(schema::derivation::activity_id.eq(schema::activity::id)), - ) - .inner_join( - schema::entity::table.on(schema::derivation::used_entity_id.eq(schema::entity::id)), - ) - .select(( - schema::derivation::activity_id, - schema::activity::external_id, - schema::entity::external_id, - schema::derivation::typ, - )) - .load::<(i32, String, String, i32)>(connection)? - { - let typ = DerivationType::try_from(typ) - .map_err(|_| StoreError::InvalidDerivationTypeRecord(typ))?; - - model.was_derived_from( - namespace_id.clone(), - typ, - EntityId::from_external_id(used_entity_id), - entity_id.clone(), - { - match activity_id { - -1 => None, - _ => Some(ActivityId::from_external_id(activity_external_id)), - } - }, - ); - } - - Ok(()) - } - - #[instrument(skip(connection))] - pub(crate) fn prov_model_for_namespace( - &self, - connection: &mut PgConnection, - namespace: &NamespaceId, - ) -> Result { - let mut model = ProvModel::default(); - let (namespaceid, nsid) = - self.namespace_by_external_id(connection, namespace.external_id_part())?; - - let agents = schema::agent::table - .filter(schema::agent::namespace_id.eq(&nsid)) - .load::(connection)?; - - for agent in agents { - self.prov_model_for_agent(agent, &namespaceid, &mut model, connection)?; - } - - let activities = schema::activity::table - .filter(schema::activity::namespace_id.eq(nsid)) - .load::(connection)?; - - for activity in activities { - self.prov_model_for_activity(activity, &namespaceid, &mut model, connection)?; - } - - let entities = schema::entity::table - .filter(schema::entity::namespace_id.eq(nsid)) - .load::(connection)?; - - for entity in entities { - self.prov_model_for_entity(entity, &namespaceid, &mut model, connection)?; - } - - Ok(model) - } - - /// Set the last fully synchronized offset - #[instrument] - pub(crate) fn set_last_block_id( - &self, - block_id: &BlockId, - tx_id: ChronicleTransactionId, - ) -> Result<(), StoreError> { - use schema::ledgersync as dsl; - - Ok(self.connection()?.build_transaction().run(|connection| { - diesel::insert_into(dsl::table) - .values(( - dsl::bc_offset.eq(block_id.to_string()), - dsl::tx_id.eq(&*tx_id.to_string()), - (dsl::sync_time.eq(Utc::now().naive_utc())), - )) - .on_conflict(dsl::tx_id) - .do_update() - .set(dsl::sync_time.eq(Utc::now().naive_utc())) - .execute(connection) - .map(|_| ()) - })?) - } - - #[instrument(skip(connection))] - pub(crate) fn use_agent( - &self, - connection: &mut PgConnection, - external_id: &ExternalId, - namespace: &ExternalId, - ) -> Result<(), StoreError> { - let (_, nsid) = self.namespace_by_external_id(connection, namespace)?; - use schema::agent::dsl; - - diesel::update(schema::agent::table.filter(dsl::current.ne(0))) - .set(dsl::current.eq(0)) - .execute(connection)?; - - diesel::update( - schema::agent::table.filter( - dsl::external_id - .eq(external_id) - .and(dsl::namespace_id.eq(nsid)), - ), - ) - .set(dsl::current.eq(1)) - .execute(connection)?; - - Ok(()) - } - - #[instrument(level = "debug", skip(connection))] - pub fn prov_model_for_agent_id( - &self, - connection: &mut PgConnection, - id: &AgentId, - ns: &ExternalId, - ) -> Result { - let agent = schema::agent::table - .inner_join(schema::namespace::dsl::namespace) - .filter(schema::agent::external_id.eq(id.external_id_part())) - .filter(schema::namespace::external_id.eq(ns)) - .select(query::Agent::as_select()) - .first(connection)?; - - let namespace = self.namespace_by_external_id(connection, ns)?.0; - - let mut model = ProvModel::default(); - self.prov_model_for_agent(agent, &namespace, &mut model, connection)?; - Ok(model) - } - - #[instrument(level = "debug", skip(connection))] - pub fn apply_prov_model_for_agent_id( - &self, - connection: &mut PgConnection, - mut model: ProvModel, - id: &AgentId, - ns: &ExternalId, - ) -> Result { - if let Some(agent) = schema::agent::table - .inner_join(schema::namespace::dsl::namespace) - .filter(schema::agent::external_id.eq(id.external_id_part())) - .filter(schema::namespace::external_id.eq(ns)) - .select(query::Agent::as_select()) - .first(connection) - .optional()? - { - let namespace = self.namespace_by_external_id(connection, ns)?.0; - self.prov_model_for_agent(agent, &namespace, &mut model, connection)?; - } - Ok(model) - } - - #[instrument(level = "debug", skip(connection))] - pub fn prov_model_for_activity_id( - &self, - connection: &mut PgConnection, - id: &ActivityId, - ns: &ExternalId, - ) -> Result { - let activity = schema::activity::table - .inner_join(schema::namespace::dsl::namespace) - .filter(schema::activity::external_id.eq(id.external_id_part())) - .filter(schema::namespace::external_id.eq(ns)) - .select(query::Activity::as_select()) - .first(connection)?; - - let namespace = self.namespace_by_external_id(connection, ns)?.0; - - let mut model = ProvModel::default(); - self.prov_model_for_activity(activity, &namespace, &mut model, connection)?; - Ok(model) - } - - #[instrument(level = "debug", skip(connection))] - pub fn apply_prov_model_for_activity_id( - &self, - connection: &mut PgConnection, - mut model: ProvModel, - id: &ActivityId, - ns: &ExternalId, - ) -> Result { - if let Some(activity) = schema::activity::table - .inner_join(schema::namespace::dsl::namespace) - .filter(schema::activity::external_id.eq(id.external_id_part())) - .filter(schema::namespace::external_id.eq(ns)) - .select(query::Activity::as_select()) - .first(connection) - .optional()? - { - let namespace = self.namespace_by_external_id(connection, ns)?.0; - self.prov_model_for_activity(activity, &namespace, &mut model, connection)?; - } - Ok(model) - } - - #[instrument(level = "debug", skip(connection))] - pub fn prov_model_for_entity_id( - &self, - connection: &mut PgConnection, - id: &EntityId, - ns: &ExternalId, - ) -> Result { - let entity = schema::entity::table - .inner_join(schema::namespace::dsl::namespace) - .filter(schema::entity::external_id.eq(id.external_id_part())) - .filter(schema::namespace::external_id.eq(ns)) - .select(query::Entity::as_select()) - .first(connection)?; - - let namespace = self.namespace_by_external_id(connection, ns)?.0; - - let mut model = ProvModel::default(); - self.prov_model_for_entity(entity, &namespace, &mut model, connection)?; - Ok(model) - } - - #[instrument(level = "debug", skip(connection))] - pub fn apply_prov_model_for_entity_id( - &self, - connection: &mut PgConnection, - mut model: ProvModel, - id: &EntityId, - ns: &ExternalId, - ) -> Result { - if let Some(entity) = schema::entity::table - .inner_join(schema::namespace::dsl::namespace) - .filter(schema::entity::external_id.eq(id.external_id_part())) - .filter(schema::namespace::external_id.eq(ns)) - .select(query::Entity::as_select()) - .first(connection) - .optional()? - { - let namespace = self.namespace_by_external_id(connection, ns)?.0; - self.prov_model_for_entity(entity, &namespace, &mut model, connection)?; - } - Ok(model) - } - - pub(crate) fn prov_model_for_usage( - &self, - connection: &mut PgConnection, - mut model: ProvModel, - id: &EntityId, - activity_id: &ActivityId, - ns: &ExternalId, - ) -> Result { - if let Some(entity) = schema::entity::table - .inner_join(schema::namespace::dsl::namespace) - .filter(schema::entity::external_id.eq(id.external_id_part())) - .filter(schema::namespace::external_id.eq(ns)) - .select(query::Entity::as_select()) - .first(connection) - .optional()? - { - if let Some(activity) = schema::activity::table - .inner_join(schema::namespace::dsl::namespace) - .filter(schema::activity::external_id.eq(id.external_id_part())) - .filter(schema::namespace::external_id.eq(ns)) - .select(query::Activity::as_select()) - .first(connection) - .optional()? - { - let namespace = self.namespace_by_external_id(connection, ns)?.0; - for used in schema::usage::table - .filter(schema::usage::activity_id.eq(activity.id)) - .order(schema::usage::activity_id.asc()) - .inner_join(schema::entity::table) - .select(schema::entity::external_id) - .load::(connection)? - { - model.used( - namespace.clone(), - activity_id, - &EntityId::from_external_id(used), - ); - } - self.prov_model_for_entity(entity, &namespace, &mut model, connection)?; - self.prov_model_for_activity(activity, &namespace, &mut model, connection)?; - } - } - Ok(model) - } + ns: &BTreeMap, + ) -> Result<(), StoreError> { + use schema::activity as dsl; + let _namespace = ns.get(namespaceid).ok_or(StoreError::InvalidNamespace {})?; + let (_, nsid) = + self.namespace_by_external_id(connection, namespaceid.external_id_part())?; + + let existing = self + .activity_by_activity_external_id_and_namespace(connection, external_id, namespaceid) + .ok(); + + let resolved_domain_type = + domaintypeid.as_ref().map(|x| x.external_id_part().clone()).or_else(|| { + existing.as_ref().and_then(|x| x.domaintype.as_ref().map(ExternalId::from)) + }); + + let resolved_started = started + .map(|x| x.naive_utc()) + .or_else(|| existing.as_ref().and_then(|x| x.started)); + + let resolved_ended = + ended.map(|x| x.naive_utc()).or_else(|| existing.as_ref().and_then(|x| x.ended)); + + diesel::insert_into(schema::activity::table) + .values(( + dsl::external_id.eq(external_id), + dsl::namespace_id.eq(nsid), + dsl::started.eq(started.map(|t| t.naive_utc())), + dsl::ended.eq(ended.map(|t| t.naive_utc())), + dsl::domaintype.eq(domaintypeid.as_ref().map(|x| x.external_id_part())), + )) + .on_conflict((dsl::external_id, dsl::namespace_id)) + .do_update() + .set(( + dsl::domaintype.eq(resolved_domain_type), + dsl::started.eq(resolved_started), + dsl::ended.eq(resolved_ended), + )) + .execute(connection)?; + + let query::Activity { id, .. } = self.activity_by_activity_external_id_and_namespace( + connection, + external_id, + namespaceid, + )?; + + diesel::insert_into(schema::activity_attribute::table) + .values( + attributes + .iter() + .map(|(_, Attribute { typ, value, .. })| query::ActivityAttribute { + activity_id: id, + typename: typ.to_owned(), + value: value.to_string(), + }) + .collect::>(), + ) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + /// Apply an agent to persistent storage, external_id + namespace are a key, so we update + /// publickey + domaintype on conflict current is a special case, only relevant to local CLI + /// context. A possibly improved design would be to store this in another table given its scope + #[instrument(level = "trace", skip(self, connection), ret(Debug))] + fn apply_agent( + &self, + connection: &mut PgConnection, + Agent { ref external_id, namespaceid, domaintypeid, attributes, .. }: &Agent, + ns: &BTreeMap, + ) -> Result<(), StoreError> { + use schema::agent::dsl; + let _namespace = ns.get(namespaceid).ok_or(StoreError::InvalidNamespace {})?; + let (_, nsid) = + self.namespace_by_external_id(connection, namespaceid.external_id_part())?; + + let existing = self + .agent_by_agent_external_id_and_namespace(connection, external_id, namespaceid) + .ok(); + + let resolved_domain_type = + domaintypeid.as_ref().map(|x| x.external_id_part().clone()).or_else(|| { + existing.as_ref().and_then(|x| x.domaintype.as_ref().map(ExternalId::from)) + }); + + diesel::insert_into(schema::agent::table) + .values(( + dsl::external_id.eq(external_id), + dsl::namespace_id.eq(nsid), + dsl::current.eq(0), + dsl::domaintype.eq(domaintypeid.as_ref().map(|x| x.external_id_part())), + )) + .on_conflict((dsl::namespace_id, dsl::external_id)) + .do_update() + .set(dsl::domaintype.eq(resolved_domain_type)) + .execute(connection)?; + + let query::Agent { id, .. } = + self.agent_by_agent_external_id_and_namespace(connection, external_id, namespaceid)?; + + diesel::insert_into(schema::agent_attribute::table) + .values( + attributes + .iter() + .map(|(_, Attribute { typ, value, .. })| query::AgentAttribute { + agent_id: id, + typename: typ.to_owned(), + value: value.to_string(), + }) + .collect::>(), + ) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + #[instrument(level = "trace", skip(self, connection), ret(Debug))] + fn apply_entity( + &self, + connection: &mut PgConnection, + Entity { namespaceid, id, external_id, domaintypeid, attributes }: &Entity, + ns: &BTreeMap, + ) -> Result<(), StoreError> { + use schema::entity::dsl; + let _namespace = ns.get(namespaceid).ok_or(StoreError::InvalidNamespace {})?; + let (_, nsid) = + self.namespace_by_external_id(connection, namespaceid.external_id_part())?; + + let existing = self + .entity_by_entity_external_id_and_namespace(connection, external_id, namespaceid) + .ok(); + + let resolved_domain_type = + domaintypeid.as_ref().map(|x| x.external_id_part().clone()).or_else(|| { + existing.as_ref().and_then(|x| x.domaintype.as_ref().map(ExternalId::from)) + }); + + diesel::insert_into(schema::entity::table) + .values(( + dsl::external_id.eq(&external_id), + dsl::namespace_id.eq(nsid), + dsl::domaintype.eq(domaintypeid.as_ref().map(|x| x.external_id_part())), + )) + .on_conflict((dsl::namespace_id, dsl::external_id)) + .do_update() + .set(dsl::domaintype.eq(resolved_domain_type)) + .execute(connection)?; + + let query::Entity { id, .. } = + self.entity_by_entity_external_id_and_namespace(connection, external_id, namespaceid)?; + + diesel::insert_into(schema::entity_attribute::table) + .values( + attributes + .iter() + .map(|(_, Attribute { typ, value, .. })| query::EntityAttribute { + entity_id: id, + typename: typ.to_owned(), + value: value.to_string(), + }) + .collect::>(), + ) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + #[instrument(level = "trace", skip(self, connection), ret(Debug))] + fn apply_has_identity( + &self, + connection: &mut PgConnection, + model: &ProvModel, + namespaceid: &NamespaceId, + agent: &AgentId, + identity: &IdentityId, + ) -> Result<(), StoreError> { + let (_, nsid) = + self.namespace_by_external_id(connection, namespaceid.external_id_part())?; + let identity = self.identity_by(connection, namespaceid, identity)?; + use schema::agent::dsl; + + diesel::update(schema::agent::table) + .filter(dsl::external_id.eq(agent.external_id_part()).and(dsl::namespace_id.eq(nsid))) + .set(dsl::identity_id.eq(identity.id)) + .execute(connection)?; + + Ok(()) + } + + #[instrument(level = "trace", skip(self, connection), ret(Debug))] + fn apply_had_identity( + &self, + connection: &mut PgConnection, + model: &ProvModel, + namespaceid: &NamespaceId, + agent: &AgentId, + identity: &IdentityId, + ) -> Result<(), StoreError> { + let identity = self.identity_by(connection, namespaceid, identity)?; + let agent = self.agent_by_agent_external_id_and_namespace( + connection, + agent.external_id_part(), + namespaceid, + )?; + use schema::hadidentity::dsl; + + diesel::insert_into(schema::hadidentity::table) + .values((dsl::agent_id.eq(agent.id), dsl::identity_id.eq(identity.id))) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + #[instrument(level = "trace", skip(self, connection), ret(Debug))] + fn apply_identity( + &self, + connection: &mut PgConnection, + Identity { id, namespaceid, public_key, .. }: &Identity, + ns: &BTreeMap, + ) -> Result<(), StoreError> { + use schema::identity::dsl; + let _namespace = ns.get(namespaceid).ok_or(StoreError::InvalidNamespace {})?; + let (_, nsid) = + self.namespace_by_external_id(connection, namespaceid.external_id_part())?; + + diesel::insert_into(schema::identity::table) + .values((dsl::namespace_id.eq(nsid), dsl::public_key.eq(public_key))) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + fn apply_model( + &self, + connection: &mut PgConnection, + model: &ProvModel, + ) -> Result<(), StoreError> { + for (_, ns) in model.namespaces.iter() { + self.apply_namespace(connection, ns)? + } + for (_, agent) in model.agents.iter() { + self.apply_agent(connection, agent, &model.namespaces)? + } + for (_, activity) in model.activities.iter() { + self.apply_activity(connection, activity, &model.namespaces)? + } + for (_, entity) in model.entities.iter() { + self.apply_entity(connection, entity, &model.namespaces)? + } + for (_, identity) in model.identities.iter() { + self.apply_identity(connection, identity, &model.namespaces)? + } + + for ((namespaceid, agent_id), (_, identity_id)) in model.has_identity.iter() { + self.apply_has_identity(connection, model, namespaceid, agent_id, identity_id)?; + } + + for ((namespaceid, agent_id), identity_id) in model.had_identity.iter() { + for (_, identity_id) in identity_id { + self.apply_had_identity(connection, model, namespaceid, agent_id, identity_id)?; + } + } + + for ((namespaceid, _), association) in model.association.iter() { + for association in association.iter() { + self.apply_was_associated_with(connection, namespaceid, association)?; + } + } + + for ((namespaceid, _), usage) in model.usage.iter() { + for usage in usage.iter() { + self.apply_used(connection, namespaceid, usage)?; + } + } + + for ((namespaceid, activity_id), was_informed_by) in model.was_informed_by.iter() { + for (_, informing_activity_id) in was_informed_by.iter() { + self.apply_was_informed_by( + connection, + namespaceid, + activity_id, + informing_activity_id, + )?; + } + } + + for ((namespaceid, _), generation) in model.generation.iter() { + for generation in generation.iter() { + self.apply_was_generated_by(connection, namespaceid, generation)?; + } + } + + for ((namespaceid, _), derivation) in model.derivation.iter() { + for derivation in derivation.iter() { + self.apply_derivation(connection, namespaceid, derivation)?; + } + } + + for ((namespaceid, _), delegation) in model.delegation.iter() { + for delegation in delegation.iter() { + self.apply_delegation(connection, namespaceid, delegation)?; + } + } + + for ((namespace_id, _), attribution) in model.attribution.iter() { + for attribution in attribution.iter() { + self.apply_was_attributed_to(connection, namespace_id, attribution)?; + } + } + + Ok(()) + } + + #[instrument(level = "trace", skip(self, connection), ret(Debug))] + fn apply_namespace( + &self, + connection: &mut PgConnection, + Namespace { ref external_id, ref uuid, .. }: &Namespace, + ) -> Result<(), StoreError> { + use schema::namespace::dsl; + diesel::insert_into(schema::namespace::table) + .values((dsl::external_id.eq(external_id), dsl::uuid.eq(uuid.to_string()))) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + pub(crate) fn apply_prov(&self, prov: &ProvModel) -> Result<(), StoreError> { + self.connection()? + .build_transaction() + .run(|connection| self.apply_model(connection, prov))?; + + Ok(()) + } + + #[instrument(skip(connection))] + fn apply_used( + &self, + connection: &mut PgConnection, + namespace: &NamespaceId, + usage: &Usage, + ) -> Result<(), StoreError> { + let storedactivity = self.activity_by_activity_external_id_and_namespace( + connection, + usage.activity_id.external_id_part(), + namespace, + )?; + + let storedentity = self.entity_by_entity_external_id_and_namespace( + connection, + usage.entity_id.external_id_part(), + namespace, + )?; + + use schema::usage::dsl as link; + diesel::insert_into(schema::usage::table) + .values(( + &link::activity_id.eq(storedactivity.id), + &link::entity_id.eq(storedentity.id), + )) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + #[instrument(skip(connection))] + fn apply_was_informed_by( + &self, + connection: &mut PgConnection, + namespace: &NamespaceId, + activity_id: &ActivityId, + informing_activity_id: &ActivityId, + ) -> Result<(), StoreError> { + let storedactivity = self.activity_by_activity_external_id_and_namespace( + connection, + activity_id.external_id_part(), + namespace, + )?; + + let storedinformingactivity = self.activity_by_activity_external_id_and_namespace( + connection, + informing_activity_id.external_id_part(), + namespace, + )?; + + use schema::wasinformedby::dsl as link; + diesel::insert_into(schema::wasinformedby::table) + .values(( + &link::activity_id.eq(storedactivity.id), + &link::informing_activity_id.eq(storedinformingactivity.id), + )) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + #[instrument(skip(self, connection))] + fn apply_was_associated_with( + &self, + connection: &mut PgConnection, + namespaceid: &common::prov::NamespaceId, + association: &Association, + ) -> Result<(), StoreError> { + let storedactivity = self.activity_by_activity_external_id_and_namespace( + connection, + association.activity_id.external_id_part(), + namespaceid, + )?; + + let storedagent = self.agent_by_agent_external_id_and_namespace( + connection, + association.agent_id.external_id_part(), + namespaceid, + )?; + + use schema::association::dsl as asoc; + let no_role = common::prov::Role("".to_string()); + diesel::insert_into(schema::association::table) + .values(( + &asoc::activity_id.eq(storedactivity.id), + &asoc::agent_id.eq(storedagent.id), + &asoc::role.eq(association.role.as_ref().unwrap_or(&no_role)), + )) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + #[instrument(skip(self, connection, namespace))] + fn apply_delegation( + &self, + connection: &mut PgConnection, + namespace: &common::prov::NamespaceId, + delegation: &Delegation, + ) -> Result<(), StoreError> { + let responsible = self.agent_by_agent_external_id_and_namespace( + connection, + delegation.responsible_id.external_id_part(), + namespace, + )?; + + let delegate = self.agent_by_agent_external_id_and_namespace( + connection, + delegation.delegate_id.external_id_part(), + namespace, + )?; + + let activity = { + if let Some(ref activity_id) = delegation.activity_id { + Some( + self.activity_by_activity_external_id_and_namespace( + connection, + activity_id.external_id_part(), + namespace, + )? + .id, + ) + } else { + None + } + }; + + use schema::delegation::dsl as link; + let no_role = common::prov::Role("".to_string()); + diesel::insert_into(schema::delegation::table) + .values(( + &link::responsible_id.eq(responsible.id), + &link::delegate_id.eq(delegate.id), + &link::activity_id.eq(activity.unwrap_or(-1)), + &link::role.eq(delegation.role.as_ref().unwrap_or(&no_role)), + )) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + #[instrument(skip(self, connection, namespace))] + fn apply_derivation( + &self, + connection: &mut PgConnection, + namespace: &common::prov::NamespaceId, + derivation: &Derivation, + ) -> Result<(), StoreError> { + let stored_generated = self.entity_by_entity_external_id_and_namespace( + connection, + derivation.generated_id.external_id_part(), + namespace, + )?; + + let stored_used = self.entity_by_entity_external_id_and_namespace( + connection, + derivation.used_id.external_id_part(), + namespace, + )?; + + let stored_activity = derivation + .activity_id + .as_ref() + .map(|activity_id| { + self.activity_by_activity_external_id_and_namespace( + connection, + activity_id.external_id_part(), + namespace, + ) + }) + .transpose()?; + + use schema::derivation::dsl as link; + diesel::insert_into(schema::derivation::table) + .values(( + &link::used_entity_id.eq(stored_used.id), + &link::generated_entity_id.eq(stored_generated.id), + &link::typ.eq(derivation.typ), + &link::activity_id.eq(stored_activity.map_or(-1, |activity| activity.id)), + )) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + #[instrument(skip(connection))] + fn apply_was_generated_by( + &self, + connection: &mut PgConnection, + namespace: &common::prov::NamespaceId, + generation: &Generation, + ) -> Result<(), StoreError> { + let storedactivity = self.activity_by_activity_external_id_and_namespace( + connection, + generation.activity_id.external_id_part(), + namespace, + )?; + + let storedentity = self.entity_by_entity_external_id_and_namespace( + connection, + generation.generated_id.external_id_part(), + namespace, + )?; + + use schema::generation::dsl as link; + diesel::insert_into(schema::generation::table) + .values(( + &link::activity_id.eq(storedactivity.id), + &link::generated_entity_id.eq(storedentity.id), + )) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + #[instrument(skip(self, connection))] + fn apply_was_attributed_to( + &self, + connection: &mut PgConnection, + namespace_id: &common::prov::NamespaceId, + attribution: &Attribution, + ) -> Result<(), StoreError> { + let stored_entity = self.entity_by_entity_external_id_and_namespace( + connection, + attribution.entity_id.external_id_part(), + namespace_id, + )?; + + let stored_agent = self.agent_by_agent_external_id_and_namespace( + connection, + attribution.agent_id.external_id_part(), + namespace_id, + )?; + + use schema::attribution::dsl as attr; + let no_role = common::prov::Role("".to_string()); + diesel::insert_into(schema::attribution::table) + .values(( + &attr::entity_id.eq(stored_entity.id), + &attr::agent_id.eq(stored_agent.id), + &attr::role.eq(attribution.role.as_ref().unwrap_or(&no_role)), + )) + .on_conflict_do_nothing() + .execute(connection)?; + + Ok(()) + } + + pub(crate) fn connection( + &self, + ) -> Result>, StoreError> { + Ok(self.pool.get()?) + } + + #[instrument(skip(connection))] + pub(crate) fn get_current_agent( + &self, + connection: &mut PgConnection, + ) -> Result { + use schema::agent::dsl; + Ok(schema::agent::table + .filter(dsl::current.ne(0)) + .first::(connection)?) + } + + /// Get the last fully synchronized offset + #[instrument] + pub(crate) fn get_last_block_id(&self) -> Result, StoreError> { + use schema::ledgersync::dsl; + self.connection()?.build_transaction().run(|connection| { + let block_id_and_tx = schema::ledgersync::table + .order_by(dsl::sync_time) + .select((dsl::bc_offset, dsl::tx_id)) + .first::<(Option, String)>(connection) + .map_err(StoreError::from)?; + + if let Some(block_id) = block_id_and_tx.0 { + Ok(Some(BlockId::try_from(block_id)?)) + } else { + Ok(None) + } + }) + } + + #[instrument(skip(connection))] + pub(crate) fn namespace_by_external_id( + &self, + connection: &mut PgConnection, + namespace: &ExternalId, + ) -> Result<(NamespaceId, i32), StoreError> { + use self::schema::namespace::dsl; + + let ns = dsl::namespace + .filter(dsl::external_id.eq(namespace)) + .select((dsl::id, dsl::external_id, dsl::uuid)) + .first::<(i32, String, String)>(connection) + .optional()? + .ok_or(StoreError::RecordNotFound {})?; + + Ok((NamespaceId::from_external_id(ns.1, Uuid::from_str(&ns.2)?), ns.0)) + } + + #[instrument(skip(connection))] + pub(crate) fn identity_by( + &self, + connection: &mut PgConnection, + namespaceid: &NamespaceId, + identity: &IdentityId, + ) -> Result { + use self::schema::identity::dsl; + let (_, nsid) = + self.namespace_by_external_id(connection, namespaceid.external_id_part())?; + let public_key = identity.public_key_part(); + + Ok(dsl::identity + .filter(dsl::public_key.eq(public_key).and(dsl::namespace_id.eq(nsid))) + .first::(connection)?) + } + + #[instrument] + pub(crate) fn new(pool: Pool>) -> Result { + Ok(Store { pool }) + } + + pub(crate) fn prov_model_for_agent( + &self, + agent: query::Agent, + namespaceid: &NamespaceId, + model: &mut ProvModel, + connection: &mut PgConnection, + ) -> Result<(), StoreError> { + debug!(?agent, "Map agent to prov"); + + let attributes = schema::agent_attribute::table + .filter(schema::agent_attribute::agent_id.eq(&agent.id)) + .load::(connection)?; + + let agentid: AgentId = AgentId::from_external_id(&agent.external_id); + model.agents.insert( + (namespaceid.clone(), agentid.clone()), + Agent { + id: agentid, + namespaceid: namespaceid.clone(), + external_id: ExternalId::from(&agent.external_id), + domaintypeid: agent.domaintype.map(DomaintypeId::from_external_id), + attributes: attributes + .into_iter() + .map(|attr| { + serde_json::from_str(&attr.value).map(|value| { + (attr.typename.clone(), Attribute { typ: attr.typename, value }) + }) + }) + .collect::, _>>()?, + }, + ); + + for (responsible, activity, role) in schema::delegation::table + .filter(schema::delegation::delegate_id.eq(agent.id)) + .inner_join( + schema::agent::table.on(schema::delegation::responsible_id.eq(schema::agent::id)), + ) + .inner_join( + schema::activity::table + .on(schema::delegation::activity_id.eq(schema::activity::id)), + ) + .order(schema::agent::external_id) + .select(( + schema::agent::external_id, + schema::activity::external_id, + schema::delegation::role, + )) + .load::<(String, String, String)>(connection)? + { + model.qualified_delegation( + namespaceid, + &AgentId::from_external_id(responsible), + &AgentId::from_external_id(&agent.external_id), + { + if activity.contains("hidden entry for Option None") { + None + } else { + Some(ActivityId::from_external_id(activity)) + } + }, + { + if role.is_empty() { + None + } else { + Some(Role(role)) + } + }, + ); + } + + Ok(()) + } + + pub(crate) fn prov_model_for_activity( + &self, + activity: query::Activity, + namespaceid: &NamespaceId, + model: &mut ProvModel, + connection: &mut PgConnection, + ) -> Result<(), StoreError> { + debug!(?activity, "Map activity to prov"); + + let attributes = schema::activity_attribute::table + .filter(schema::activity_attribute::activity_id.eq(&activity.id)) + .load::(connection)?; + + let id: ActivityId = ActivityId::from_external_id(&activity.external_id); + model.activities.insert( + (namespaceid.clone(), id.clone()), + Activity { + id: id.clone(), + namespaceid: namespaceid.clone(), + external_id: activity.external_id.into(), + started: activity.started.map(|x| Utc.from_utc_datetime(&x)), + ended: activity.ended.map(|x| Utc.from_utc_datetime(&x)), + domaintypeid: activity.domaintype.map(DomaintypeId::from_external_id), + attributes: attributes + .into_iter() + .map(|attr| { + serde_json::from_str(&attr.value).map(|value| { + (attr.typename.clone(), Attribute { typ: attr.typename, value }) + }) + }) + .collect::, _>>()?, + }, + ); + + for generation in schema::generation::table + .filter(schema::generation::activity_id.eq(activity.id)) + .order(schema::generation::activity_id.asc()) + .inner_join(schema::entity::table) + .select(schema::entity::external_id) + .load::(connection)? + { + model.was_generated_by( + namespaceid.clone(), + &EntityId::from_external_id(generation), + &id, + ); + } + + for used in schema::usage::table + .filter(schema::usage::activity_id.eq(activity.id)) + .order(schema::usage::activity_id.asc()) + .inner_join(schema::entity::table) + .select(schema::entity::external_id) + .load::(connection)? + { + model.used(namespaceid.clone(), &id, &EntityId::from_external_id(used)); + } + + for wasinformedby in schema::wasinformedby::table + .filter(schema::wasinformedby::activity_id.eq(activity.id)) + .inner_join( + schema::activity::table + .on(schema::wasinformedby::informing_activity_id.eq(schema::activity::id)), + ) + .select(schema::activity::external_id) + .load::(connection)? + { + model.was_informed_by( + namespaceid.clone(), + &id, + &ActivityId::from_external_id(wasinformedby), + ); + } + + for (agent, role) in schema::association::table + .filter(schema::association::activity_id.eq(activity.id)) + .order(schema::association::activity_id.asc()) + .inner_join(schema::agent::table) + .select((schema::agent::external_id, schema::association::role)) + .load::<(String, String)>(connection)? + { + model.qualified_association(namespaceid, &id, &AgentId::from_external_id(agent), { + if role.is_empty() { + None + } else { + Some(Role(role)) + } + }); + } + + Ok(()) + } + + pub(crate) fn prov_model_for_entity( + &self, + entity: query::Entity, + namespace_id: &NamespaceId, + model: &mut ProvModel, + connection: &mut PgConnection, + ) -> Result<(), StoreError> { + debug!(?entity, "Map entity to prov"); + + let query::Entity { id, namespace_id: _, domaintype, external_id } = entity; + + let entity_id = EntityId::from_external_id(&external_id); + + for (agent, role) in schema::attribution::table + .filter(schema::attribution::entity_id.eq(&id)) + .order(schema::attribution::entity_id.asc()) + .inner_join(schema::agent::table) + .select((schema::agent::external_id, schema::attribution::role)) + .load::<(String, String)>(connection)? + { + model.qualified_attribution( + namespace_id, + &entity_id, + &AgentId::from_external_id(agent), + { + if role.is_empty() { + None + } else { + Some(Role(role)) + } + }, + ); + } + + let attributes = schema::entity_attribute::table + .filter(schema::entity_attribute::entity_id.eq(&id)) + .load::(connection)?; + + model.entities.insert( + (namespace_id.clone(), entity_id.clone()), + Entity { + id: entity_id.clone(), + namespaceid: namespace_id.clone(), + external_id: external_id.into(), + domaintypeid: domaintype.map(DomaintypeId::from_external_id), + attributes: attributes + .into_iter() + .map(|attr| { + serde_json::from_str(&attr.value).map(|value| { + (attr.typename.clone(), Attribute { typ: attr.typename, value }) + }) + }) + .collect::, _>>()?, + }, + ); + + for (activity_id, activity_external_id, used_entity_id, typ) in schema::derivation::table + .filter(schema::derivation::generated_entity_id.eq(&id)) + .order(schema::derivation::generated_entity_id.asc()) + .inner_join( + schema::activity::table + .on(schema::derivation::activity_id.eq(schema::activity::id)), + ) + .inner_join( + schema::entity::table.on(schema::derivation::used_entity_id.eq(schema::entity::id)), + ) + .select(( + schema::derivation::activity_id, + schema::activity::external_id, + schema::entity::external_id, + schema::derivation::typ, + )) + .load::<(i32, String, String, i32)>(connection)? + { + let typ = DerivationType::try_from(typ) + .map_err(|_| StoreError::InvalidDerivationTypeRecord(typ))?; + + model.was_derived_from( + namespace_id.clone(), + typ, + EntityId::from_external_id(used_entity_id), + entity_id.clone(), + { + match activity_id { + -1 => None, + _ => Some(ActivityId::from_external_id(activity_external_id)), + } + }, + ); + } + + Ok(()) + } + + #[instrument(skip(connection))] + pub(crate) fn prov_model_for_namespace( + &self, + connection: &mut PgConnection, + namespace: &NamespaceId, + ) -> Result { + let mut model = ProvModel::default(); + let (namespaceid, nsid) = + self.namespace_by_external_id(connection, namespace.external_id_part())?; + + let agents = schema::agent::table + .filter(schema::agent::namespace_id.eq(&nsid)) + .load::(connection)?; + + for agent in agents { + self.prov_model_for_agent(agent, &namespaceid, &mut model, connection)?; + } + + let activities = schema::activity::table + .filter(schema::activity::namespace_id.eq(nsid)) + .load::(connection)?; + + for activity in activities { + self.prov_model_for_activity(activity, &namespaceid, &mut model, connection)?; + } + + let entities = schema::entity::table + .filter(schema::entity::namespace_id.eq(nsid)) + .load::(connection)?; + + for entity in entities { + self.prov_model_for_entity(entity, &namespaceid, &mut model, connection)?; + } + + Ok(model) + } + + /// Set the last fully synchronized offset + #[instrument] + pub(crate) fn set_last_block_id( + &self, + block_id: &BlockId, + tx_id: ChronicleTransactionId, + ) -> Result<(), StoreError> { + use schema::ledgersync as dsl; + + Ok(self.connection()?.build_transaction().run(|connection| { + diesel::insert_into(dsl::table) + .values(( + dsl::bc_offset.eq(block_id.to_string()), + dsl::tx_id.eq(&*tx_id.to_string()), + (dsl::sync_time.eq(Utc::now().naive_utc())), + )) + .on_conflict(dsl::tx_id) + .do_update() + .set(dsl::sync_time.eq(Utc::now().naive_utc())) + .execute(connection) + .map(|_| ()) + })?) + } + + #[instrument(skip(connection))] + pub(crate) fn use_agent( + &self, + connection: &mut PgConnection, + external_id: &ExternalId, + namespace: &ExternalId, + ) -> Result<(), StoreError> { + let (_, nsid) = self.namespace_by_external_id(connection, namespace)?; + use schema::agent::dsl; + + diesel::update(schema::agent::table.filter(dsl::current.ne(0))) + .set(dsl::current.eq(0)) + .execute(connection)?; + + diesel::update( + schema::agent::table + .filter(dsl::external_id.eq(external_id).and(dsl::namespace_id.eq(nsid))), + ) + .set(dsl::current.eq(1)) + .execute(connection)?; + + Ok(()) + } + + #[instrument(level = "debug", skip(connection))] + pub fn prov_model_for_agent_id( + &self, + connection: &mut PgConnection, + id: &AgentId, + ns: &ExternalId, + ) -> Result { + let agent = schema::agent::table + .inner_join(schema::namespace::dsl::namespace) + .filter(schema::agent::external_id.eq(id.external_id_part())) + .filter(schema::namespace::external_id.eq(ns)) + .select(query::Agent::as_select()) + .first(connection)?; + + let namespace = self.namespace_by_external_id(connection, ns)?.0; + + let mut model = ProvModel::default(); + self.prov_model_for_agent(agent, &namespace, &mut model, connection)?; + Ok(model) + } + + #[instrument(level = "debug", skip(connection))] + pub fn apply_prov_model_for_agent_id( + &self, + connection: &mut PgConnection, + mut model: ProvModel, + id: &AgentId, + ns: &ExternalId, + ) -> Result { + if let Some(agent) = schema::agent::table + .inner_join(schema::namespace::dsl::namespace) + .filter(schema::agent::external_id.eq(id.external_id_part())) + .filter(schema::namespace::external_id.eq(ns)) + .select(query::Agent::as_select()) + .first(connection) + .optional()? + { + let namespace = self.namespace_by_external_id(connection, ns)?.0; + self.prov_model_for_agent(agent, &namespace, &mut model, connection)?; + } + Ok(model) + } + + #[instrument(level = "debug", skip(connection))] + pub fn prov_model_for_activity_id( + &self, + connection: &mut PgConnection, + id: &ActivityId, + ns: &ExternalId, + ) -> Result { + let activity = schema::activity::table + .inner_join(schema::namespace::dsl::namespace) + .filter(schema::activity::external_id.eq(id.external_id_part())) + .filter(schema::namespace::external_id.eq(ns)) + .select(query::Activity::as_select()) + .first(connection)?; + + let namespace = self.namespace_by_external_id(connection, ns)?.0; + + let mut model = ProvModel::default(); + self.prov_model_for_activity(activity, &namespace, &mut model, connection)?; + Ok(model) + } + + #[instrument(level = "debug", skip(connection))] + pub fn apply_prov_model_for_activity_id( + &self, + connection: &mut PgConnection, + mut model: ProvModel, + id: &ActivityId, + ns: &ExternalId, + ) -> Result { + if let Some(activity) = schema::activity::table + .inner_join(schema::namespace::dsl::namespace) + .filter(schema::activity::external_id.eq(id.external_id_part())) + .filter(schema::namespace::external_id.eq(ns)) + .select(query::Activity::as_select()) + .first(connection) + .optional()? + { + let namespace = self.namespace_by_external_id(connection, ns)?.0; + self.prov_model_for_activity(activity, &namespace, &mut model, connection)?; + } + Ok(model) + } + + #[instrument(level = "debug", skip(connection))] + pub fn prov_model_for_entity_id( + &self, + connection: &mut PgConnection, + id: &EntityId, + ns: &ExternalId, + ) -> Result { + let entity = schema::entity::table + .inner_join(schema::namespace::dsl::namespace) + .filter(schema::entity::external_id.eq(id.external_id_part())) + .filter(schema::namespace::external_id.eq(ns)) + .select(query::Entity::as_select()) + .first(connection)?; + + let namespace = self.namespace_by_external_id(connection, ns)?.0; + + let mut model = ProvModel::default(); + self.prov_model_for_entity(entity, &namespace, &mut model, connection)?; + Ok(model) + } + + #[instrument(level = "debug", skip(connection))] + pub fn apply_prov_model_for_entity_id( + &self, + connection: &mut PgConnection, + mut model: ProvModel, + id: &EntityId, + ns: &ExternalId, + ) -> Result { + if let Some(entity) = schema::entity::table + .inner_join(schema::namespace::dsl::namespace) + .filter(schema::entity::external_id.eq(id.external_id_part())) + .filter(schema::namespace::external_id.eq(ns)) + .select(query::Entity::as_select()) + .first(connection) + .optional()? + { + let namespace = self.namespace_by_external_id(connection, ns)?.0; + self.prov_model_for_entity(entity, &namespace, &mut model, connection)?; + } + Ok(model) + } + + pub(crate) fn prov_model_for_usage( + &self, + connection: &mut PgConnection, + mut model: ProvModel, + id: &EntityId, + activity_id: &ActivityId, + ns: &ExternalId, + ) -> Result { + if let Some(entity) = schema::entity::table + .inner_join(schema::namespace::dsl::namespace) + .filter(schema::entity::external_id.eq(id.external_id_part())) + .filter(schema::namespace::external_id.eq(ns)) + .select(query::Entity::as_select()) + .first(connection) + .optional()? + { + if let Some(activity) = schema::activity::table + .inner_join(schema::namespace::dsl::namespace) + .filter(schema::activity::external_id.eq(id.external_id_part())) + .filter(schema::namespace::external_id.eq(ns)) + .select(query::Activity::as_select()) + .first(connection) + .optional()? + { + let namespace = self.namespace_by_external_id(connection, ns)?.0; + for used in schema::usage::table + .filter(schema::usage::activity_id.eq(activity.id)) + .order(schema::usage::activity_id.asc()) + .inner_join(schema::entity::table) + .select(schema::entity::external_id) + .load::(connection)? + { + model.used(namespace.clone(), activity_id, &EntityId::from_external_id(used)); + } + self.prov_model_for_entity(entity, &namespace, &mut model, connection)?; + self.prov_model_for_activity(activity, &namespace, &mut model, connection)?; + } + } + Ok(model) + } } diff --git a/crates/api/src/persistence/query.rs b/crates/api/src/persistence/query.rs index f4e2a9e88..b4dc561eb 100644 --- a/crates/api/src/persistence/query.rs +++ b/crates/api/src/persistence/query.rs @@ -4,107 +4,107 @@ use diesel::prelude::*; #[derive(Queryable)] pub struct Namespace { - pub external_id: String, - pub uuid: String, + pub external_id: String, + pub uuid: String, } #[derive(Queryable)] pub struct LedgerSync { - pub bc_offset: String, - pub sync_time: Option, + pub bc_offset: String, + pub sync_time: Option, } #[derive(Insertable)] #[diesel(table_name = namespace)] pub struct NewNamespace<'a> { - pub external_id: &'a str, - pub uuid: &'a str, + pub external_id: &'a str, + pub uuid: &'a str, } #[derive(Insertable)] #[diesel(table_name = ledgersync)] pub struct NewOffset<'a> { - pub bc_offset: &'a str, - pub sync_time: Option, + pub bc_offset: &'a str, + pub sync_time: Option, } #[derive(Insertable, Queryable, Selectable)] #[diesel(table_name = entity_attribute)] pub struct EntityAttribute { - pub entity_id: i32, - pub typename: String, - pub value: String, + pub entity_id: i32, + pub typename: String, + pub value: String, } #[derive(Insertable, Queryable, Selectable)] #[diesel(table_name = activity_attribute)] pub struct ActivityAttribute { - pub activity_id: i32, - pub typename: String, - pub value: String, + pub activity_id: i32, + pub typename: String, + pub value: String, } #[derive(Insertable, Queryable, Selectable)] #[diesel(table_name = agent_attribute)] pub struct AgentAttribute { - pub agent_id: i32, - pub typename: String, - pub value: String, + pub agent_id: i32, + pub typename: String, + pub value: String, } #[derive(Insertable)] #[diesel(table_name = activity)] pub struct NewActivity<'a> { - pub external_id: &'a str, - pub namespace_id: i32, - pub started: Option, - pub ended: Option, - pub domaintype: Option<&'a str>, + pub external_id: &'a str, + pub namespace_id: i32, + pub started: Option, + pub ended: Option, + pub domaintype: Option<&'a str>, } #[derive(Debug, Queryable, Selectable)] #[diesel(table_name = agent)] pub struct Agent { - pub id: i32, - pub external_id: String, - pub namespace_id: i32, - pub domaintype: Option, - pub current: i32, - pub identity_id: Option, + pub id: i32, + pub external_id: String, + pub namespace_id: i32, + pub domaintype: Option, + pub current: i32, + pub identity_id: Option, } #[derive(Debug, Queryable)] pub struct Identity { - pub id: i32, - pub namespace_id: i32, - pub public_key: String, + pub id: i32, + pub namespace_id: i32, + pub public_key: String, } #[derive(Debug, Queryable, Selectable)] #[diesel(table_name = activity)] pub struct Activity { - pub id: i32, - pub external_id: String, - pub namespace_id: i32, - pub domaintype: Option, - pub started: Option, - pub ended: Option, + pub id: i32, + pub external_id: String, + pub namespace_id: i32, + pub domaintype: Option, + pub started: Option, + pub ended: Option, } #[derive(Debug, Queryable, Selectable)] #[diesel(table_name = entity)] pub struct Entity { - pub id: i32, - pub external_id: String, - pub namespace_id: i32, - pub domaintype: Option, + pub id: i32, + pub external_id: String, + pub namespace_id: i32, + pub domaintype: Option, } #[derive(Insertable, Queryable, Selectable)] #[diesel(table_name = agent)] pub struct NewAgent<'a> { - pub external_id: &'a str, - pub namespace_id: i32, - pub current: i32, - pub domaintype: Option<&'a str>, + pub external_id: &'a str, + pub namespace_id: i32, + pub current: i32, + pub domaintype: Option<&'a str>, } diff --git a/crates/api/src/persistence/schema.rs b/crates/api/src/persistence/schema.rs index 8e0a9394f..388e8ca4b 100644 --- a/crates/api/src/persistence/schema.rs +++ b/crates/api/src/persistence/schema.rs @@ -1,144 +1,144 @@ // @generated automatically by Diesel CLI. diesel::table! { - activity (id) { - id -> Int4, - external_id -> Text, - namespace_id -> Int4, - domaintype -> Nullable, - started -> Nullable, - ended -> Nullable, - } + activity (id) { + id -> Int4, + external_id -> Text, + namespace_id -> Int4, + domaintype -> Nullable, + started -> Nullable, + ended -> Nullable, + } } diesel::table! { - activity_attribute (activity_id, typename) { - activity_id -> Int4, - typename -> Text, - value -> Text, - } + activity_attribute (activity_id, typename) { + activity_id -> Int4, + typename -> Text, + value -> Text, + } } diesel::table! { - agent (id) { - id -> Int4, - external_id -> Text, - namespace_id -> Int4, - domaintype -> Nullable, - current -> Int4, - identity_id -> Nullable, - } + agent (id) { + id -> Int4, + external_id -> Text, + namespace_id -> Int4, + domaintype -> Nullable, + current -> Int4, + identity_id -> Nullable, + } } diesel::table! { - agent_attribute (agent_id, typename) { - agent_id -> Int4, - typename -> Text, - value -> Text, - } + agent_attribute (agent_id, typename) { + agent_id -> Int4, + typename -> Text, + value -> Text, + } } diesel::table! { - association (agent_id, activity_id, role) { - agent_id -> Int4, - activity_id -> Int4, - role -> Text, - } + association (agent_id, activity_id, role) { + agent_id -> Int4, + activity_id -> Int4, + role -> Text, + } } diesel::table! { - attribution (agent_id, entity_id, role) { - agent_id -> Int4, - entity_id -> Int4, - role -> Text, - } + attribution (agent_id, entity_id, role) { + agent_id -> Int4, + entity_id -> Int4, + role -> Text, + } } diesel::table! { - delegation (responsible_id, delegate_id, activity_id, role) { - delegate_id -> Int4, - responsible_id -> Int4, - activity_id -> Int4, - role -> Text, - } + delegation (responsible_id, delegate_id, activity_id, role) { + delegate_id -> Int4, + responsible_id -> Int4, + activity_id -> Int4, + role -> Text, + } } diesel::table! { - derivation (activity_id, used_entity_id, generated_entity_id, typ) { - activity_id -> Int4, - generated_entity_id -> Int4, - used_entity_id -> Int4, - typ -> Int4, - } + derivation (activity_id, used_entity_id, generated_entity_id, typ) { + activity_id -> Int4, + generated_entity_id -> Int4, + used_entity_id -> Int4, + typ -> Int4, + } } diesel::table! { - entity (id) { - id -> Int4, - external_id -> Text, - namespace_id -> Int4, - domaintype -> Nullable, - } + entity (id) { + id -> Int4, + external_id -> Text, + namespace_id -> Int4, + domaintype -> Nullable, + } } diesel::table! { - entity_attribute (entity_id, typename) { - entity_id -> Int4, - typename -> Text, - value -> Text, - } + entity_attribute (entity_id, typename) { + entity_id -> Int4, + typename -> Text, + value -> Text, + } } diesel::table! { - generation (activity_id, generated_entity_id) { - activity_id -> Int4, - generated_entity_id -> Int4, - } + generation (activity_id, generated_entity_id) { + activity_id -> Int4, + generated_entity_id -> Int4, + } } diesel::table! { - hadidentity (agent_id, identity_id) { - agent_id -> Int4, - identity_id -> Int4, - } + hadidentity (agent_id, identity_id) { + agent_id -> Int4, + identity_id -> Int4, + } } diesel::table! { - identity (id) { - id -> Int4, - namespace_id -> Int4, - public_key -> Text, - } + identity (id) { + id -> Int4, + namespace_id -> Int4, + public_key -> Text, + } } diesel::table! { - ledgersync (tx_id) { - tx_id -> Text, - bc_offset -> Nullable, - sync_time -> Nullable, - } + ledgersync (tx_id) { + tx_id -> Text, + bc_offset -> Nullable, + sync_time -> Nullable, + } } diesel::table! { - namespace (id) { - id -> Int4, - external_id -> Text, - uuid -> Text, - } + namespace (id) { + id -> Int4, + external_id -> Text, + uuid -> Text, + } } diesel::table! { - usage (activity_id, entity_id) { - activity_id -> Int4, - entity_id -> Int4, - } + usage (activity_id, entity_id) { + activity_id -> Int4, + entity_id -> Int4, + } } diesel::table! { - wasinformedby (activity_id, informing_activity_id) { - activity_id -> Int4, - informing_activity_id -> Int4, - } + wasinformedby (activity_id, informing_activity_id) { + activity_id -> Int4, + informing_activity_id -> Int4, + } } diesel::joinable!(activity -> namespace (namespace_id)); @@ -163,21 +163,21 @@ diesel::joinable!(usage -> activity (activity_id)); diesel::joinable!(usage -> entity (entity_id)); diesel::allow_tables_to_appear_in_same_query!( - activity, - activity_attribute, - agent, - agent_attribute, - association, - attribution, - delegation, - derivation, - entity, - entity_attribute, - generation, - hadidentity, - identity, - ledgersync, - namespace, - usage, - wasinformedby, + activity, + activity_attribute, + agent, + agent_attribute, + association, + attribution, + delegation, + derivation, + entity, + entity_attribute, + generation, + hadidentity, + identity, + ledgersync, + namespace, + usage, + wasinformedby, ); diff --git a/crates/chronicle-domain-lint/build.rs b/crates/chronicle-domain-lint/build.rs index 5a1d86bbe..afb2c9546 100644 --- a/crates/chronicle-domain-lint/build.rs +++ b/crates/chronicle-domain-lint/build.rs @@ -1,8 +1,8 @@ fn main() { - //Create a .VERSION file containing 'local' if it does not exist + //Create a .VERSION file containing 'local' if it does not exist - let version_file = std::path::Path::new("../../.VERSION"); - if !version_file.exists() { - std::fs::write(version_file, "local").expect("Unable to write file"); - } + let version_file = std::path::Path::new("../../.VERSION"); + if !version_file.exists() { + std::fs::write(version_file, "local").expect("Unable to write file"); + } } diff --git a/crates/chronicle-domain-lint/src/main.rs b/crates/chronicle-domain-lint/src/main.rs index ac3628b7f..35086d199 100644 --- a/crates/chronicle-domain-lint/src/main.rs +++ b/crates/chronicle-domain-lint/src/main.rs @@ -2,21 +2,21 @@ use chronicle::codegen::linter::check_files; use clap::{Arg, Command, ValueHint}; fn main() { - let version = env!("CARGO_PKG_VERSION"); - let cli = Command::new("chronicle-domain-lint") - .version(version) - .author("Blockchain Technology Partners") - .arg( - Arg::new("filenames") - .value_hint(ValueHint::FilePath) - .required(true) - .multiple_values(true) - .min_values(1) - .help("domain definition files for linting"), - ); + let version = env!("CARGO_PKG_VERSION"); + let cli = Command::new("chronicle-domain-lint") + .version(version) + .author("Blockchain Technology Partners") + .arg( + Arg::new("filenames") + .value_hint(ValueHint::FilePath) + .required(true) + .multiple_values(true) + .min_values(1) + .help("domain definition files for linting"), + ); - let matches = cli.get_matches(); - let filenames = matches.values_of("filenames").unwrap().collect(); - check_files(filenames); - println!("successful: no domain definition errors detected"); + let matches = cli.get_matches(); + let filenames = matches.values_of("filenames").unwrap().collect(); + check_files(filenames); + println!("successful: no domain definition errors detected"); } diff --git a/crates/chronicle-domain-test/build.rs b/crates/chronicle-domain-test/build.rs index 80f5bf4be..34b4821aa 100644 --- a/crates/chronicle-domain-test/build.rs +++ b/crates/chronicle-domain-test/build.rs @@ -3,7 +3,7 @@ use std::process::Command; use chronicle::{codegen::ChronicleDomainDef, generate_chronicle_domain_schema}; fn main() { - let s = r#" + let s = r#" name: "airworthiness" attributes: CertId: @@ -47,14 +47,14 @@ fn main() { - MANUFACTURER - SUBMITTER "# - .to_string(); + .to_string(); - let model = ChronicleDomainDef::from_input_string(&s).unwrap(); + let model = ChronicleDomainDef::from_input_string(&s).unwrap(); - generate_chronicle_domain_schema(model, "src/generated.rs"); + generate_chronicle_domain_schema(model, "src/generated.rs"); - Command::new("cargo") - .args(["fmt", "--", "src/generated.rs"]) - .output() - .expect("formatting"); + Command::new("cargo") + .args(["fmt", "--", "src/generated.rs"]) + .output() + .expect("formatting"); } diff --git a/crates/chronicle-domain-test/src/test.rs b/crates/chronicle-domain-test/src/test.rs index 8f775ae22..9d692e748 100644 --- a/crates/chronicle-domain-test/src/test.rs +++ b/crates/chronicle-domain-test/src/test.rs @@ -1,15 +1,16 @@ use chronicle::{ - api::chronicle_graphql::ChronicleGraphQl, bootstrap, codegen::ChronicleDomainDef, tokio, + api::chronicle_graphql::ChronicleGraphQl, bootstrap, codegen::ChronicleDomainDef, tokio, }; use generated::{Mutation, Query}; #[allow(dead_code)] mod generated; -///Entry point here is jigged a little, as we want to run unit tests, see chronicle-untyped for the actual pattern +///Entry point here is jigged a little, as we want to run unit tests, see chronicle-untyped for the +/// actual pattern #[tokio::main] pub async fn main() { - let s = r#" + let s = r#" name: "airworthiness" attributes: CertId: @@ -53,178 +54,169 @@ pub async fn main() { - MANUFACTURER - SUBMITTER "# - .to_string(); + .to_string(); - let model = ChronicleDomainDef::from_input_string(&s).unwrap(); + let model = ChronicleDomainDef::from_input_string(&s).unwrap(); - bootstrap(model, ChronicleGraphQl::new(Query, Mutation)).await + bootstrap(model, ChronicleGraphQl::new(Query, Mutation)).await } #[cfg(test)] mod test { - use super::{Mutation, Query}; - use async_stl_client::prost::Message; - use chronicle::{ - api::{ - chronicle_graphql::{OpaCheck, Store, Subscription}, - inmem::EmbeddedChronicleTp, - Api, UuidGen, - }, - async_graphql::{Request, Response, Schema}, - chrono::{NaiveDate, TimeZone, Utc}, - common::{ - database::TemporaryDatabase, - identity::AuthId, - k256::sha2::{Digest, Sha256}, - opa::{CliPolicyLoader, ExecutorContext}, - }, - serde_json, tokio, - uuid::Uuid, - }; - use chronicle_signing::{ - chronicle_secret_names, ChronicleSecretsOptions, ChronicleSigning, BATCHER_NAMESPACE, - CHRONICLE_NAMESPACE, - }; - use core::future::Future; - use opa_tp_protocol::state::{policy_address, policy_meta_address, PolicyMeta}; - use std::time::Duration; - - #[derive(Debug, Clone)] - struct SameUuid; - - impl UuidGen for SameUuid { - fn uuid() -> Uuid { - Uuid::parse_str("5a0ab5b8-eeb7-4812-9fe3-6dd69bd20cea").unwrap() - } - } - - async fn test_schema<'a>() -> (Schema, TemporaryDatabase<'a>) { - let loader = CliPolicyLoader::from_embedded_policy( - "allow_transactions", - "allow_transactions.allowed_users", - ) - .unwrap(); - let opa_executor = ExecutorContext::from_loader(&loader).unwrap(); - - test_schema_with_opa(opa_executor).await - } - - async fn test_schema_blocked_api<'a>( - ) -> (Schema, TemporaryDatabase<'a>) { - let loader = CliPolicyLoader::from_embedded_policy( - "allow_transactions", - "allow_transactions.deny_all", - ) - .unwrap(); - let opa_executor = ExecutorContext::from_loader(&loader).unwrap(); - - test_schema_with_opa(opa_executor).await - } - - async fn test_schema_with_opa<'a>( - opa_executor: ExecutorContext, - ) -> (Schema, TemporaryDatabase<'a>) { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - - let signing = ChronicleSigning::new( - chronicle_secret_names(), - vec![ - ( - CHRONICLE_NAMESPACE.to_string(), - ChronicleSecretsOptions::test_keys(), - ), - ( - BATCHER_NAMESPACE.to_string(), - ChronicleSecretsOptions::test_keys(), - ), - ], - ) - .await - .unwrap(); - - let buf = async_stl_client::messages::Setting { - entries: vec![async_stl_client::messages::setting::Entry { - key: "chronicle.opa.policy_name".to_string(), - value: "allow_transactions".to_string(), - }], - } - .encode_to_vec(); - - let setting_id = ( - chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.policy_name"), - buf, - ); - let buf = async_stl_client::messages::Setting { - entries: vec![async_stl_client::messages::setting::Entry { - key: "chronicle.opa.entrypoint".to_string(), - value: "allow_transactions.allowed_users".to_string(), - }], - } - .encode_to_vec(); - - let setting_entrypoint = ( - chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.entrypoint"), - buf, - ); - - let d = env!("CARGO_MANIFEST_DIR").to_owned() + "/../../policies/bundle.tar.gz"; - let bin = std::fs::read(d).unwrap(); - - let meta = PolicyMeta { - id: "allow_transactions".to_string(), - hash: hex::encode(Sha256::digest(&bin)), - policy_address: policy_address("allow_transactions"), - }; - - let tp = EmbeddedChronicleTp::new_with_state( - vec![ - setting_id, - setting_entrypoint, - (policy_address("allow_transactions"), bin), - ( - policy_meta_address("allow_transactions"), - serde_json::to_vec(&meta).unwrap(), - ), - ] - .into_iter() - .collect(), - ) - .unwrap(); - - let ledger = tp.ledger.clone(); - - let database = TemporaryDatabase::default(); - let pool = database.connection_pool().unwrap(); - let liveness_check_interval = None; - - let dispatch = Api::new( - pool.clone(), - ledger, - SameUuid, - signing, - vec![], - None, - liveness_check_interval, - ) - .await - .unwrap(); - - let schema = Schema::build(Query, Mutation, Subscription) - .extension(OpaCheck { claim_parser: None }) - .data(Store::new(pool)) - .data(dispatch) - .data(AuthId::chronicle()) - .data(opa_executor) - .finish(); - - (schema, database) - } - - #[tokio::test] - async fn accept_long_form_including_original_name_iris() { - let (schema, _database) = test_schema().await; - - insta::assert_toml_snapshot!(schema + use super::{Mutation, Query}; + use async_stl_client::prost::Message; + use chronicle::{ + api::{ + chronicle_graphql::{OpaCheck, Store, Subscription}, + inmem::EmbeddedChronicleTp, + Api, UuidGen, + }, + async_graphql::{Request, Response, Schema}, + chrono::{NaiveDate, TimeZone, Utc}, + common::{ + database::TemporaryDatabase, + identity::AuthId, + k256::sha2::{Digest, Sha256}, + opa::{CliPolicyLoader, ExecutorContext}, + }, + serde_json, tokio, + uuid::Uuid, + }; + use chronicle_signing::{ + chronicle_secret_names, ChronicleSecretsOptions, ChronicleSigning, BATCHER_NAMESPACE, + CHRONICLE_NAMESPACE, + }; + use core::future::Future; + use opa_tp_protocol::state::{policy_address, policy_meta_address, PolicyMeta}; + use std::time::Duration; + + #[derive(Debug, Clone)] + struct SameUuid; + + impl UuidGen for SameUuid { + fn uuid() -> Uuid { + Uuid::parse_str("5a0ab5b8-eeb7-4812-9fe3-6dd69bd20cea").unwrap() + } + } + + async fn test_schema<'a>() -> (Schema, TemporaryDatabase<'a>) { + let loader = CliPolicyLoader::from_embedded_policy( + "allow_transactions", + "allow_transactions.allowed_users", + ) + .unwrap(); + let opa_executor = ExecutorContext::from_loader(&loader).unwrap(); + + test_schema_with_opa(opa_executor).await + } + + async fn test_schema_blocked_api<'a>( + ) -> (Schema, TemporaryDatabase<'a>) { + let loader = CliPolicyLoader::from_embedded_policy( + "allow_transactions", + "allow_transactions.deny_all", + ) + .unwrap(); + let opa_executor = ExecutorContext::from_loader(&loader).unwrap(); + + test_schema_with_opa(opa_executor).await + } + + async fn test_schema_with_opa<'a>( + opa_executor: ExecutorContext, + ) -> (Schema, TemporaryDatabase<'a>) { + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + + let signing = ChronicleSigning::new( + chronicle_secret_names(), + vec![ + (CHRONICLE_NAMESPACE.to_string(), ChronicleSecretsOptions::test_keys()), + (BATCHER_NAMESPACE.to_string(), ChronicleSecretsOptions::test_keys()), + ], + ) + .await + .unwrap(); + + let buf = async_stl_client::messages::Setting { + entries: vec![async_stl_client::messages::setting::Entry { + key: "chronicle.opa.policy_name".to_string(), + value: "allow_transactions".to_string(), + }], + } + .encode_to_vec(); + + let setting_id = ( + chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.policy_name"), + buf, + ); + let buf = async_stl_client::messages::Setting { + entries: vec![async_stl_client::messages::setting::Entry { + key: "chronicle.opa.entrypoint".to_string(), + value: "allow_transactions.allowed_users".to_string(), + }], + } + .encode_to_vec(); + + let setting_entrypoint = ( + chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.entrypoint"), + buf, + ); + + let d = env!("CARGO_MANIFEST_DIR").to_owned() + "/../../policies/bundle.tar.gz"; + let bin = std::fs::read(d).unwrap(); + + let meta = PolicyMeta { + id: "allow_transactions".to_string(), + hash: hex::encode(Sha256::digest(&bin)), + policy_address: policy_address("allow_transactions"), + }; + + let tp = EmbeddedChronicleTp::new_with_state( + vec![ + setting_id, + setting_entrypoint, + (policy_address("allow_transactions"), bin), + (policy_meta_address("allow_transactions"), serde_json::to_vec(&meta).unwrap()), + ] + .into_iter() + .collect(), + ) + .unwrap(); + + let ledger = tp.ledger.clone(); + + let database = TemporaryDatabase::default(); + let pool = database.connection_pool().unwrap(); + let liveness_check_interval = None; + + let dispatch = Api::new( + pool.clone(), + ledger, + SameUuid, + signing, + vec![], + None, + liveness_check_interval, + ) + .await + .unwrap(); + + let schema = Schema::build(Query, Mutation, Subscription) + .extension(OpaCheck { claim_parser: None }) + .data(Store::new(pool)) + .data(dispatch) + .data(AuthId::chronicle()) + .data(opa_executor) + .finish(); + + (schema, database) + } + + #[tokio::test] + async fn accept_long_form_including_original_name_iris() { + let (schema, _database) = test_schema().await; + + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -243,9 +235,9 @@ mod test { context = 'chronicle:agent:testagent' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -282,13 +274,13 @@ mod test { } } "###); - } + } - #[tokio::test] - async fn activity_timeline_no_duplicates() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn activity_timeline_no_duplicates() { + let (schema, _database) = test_schema().await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" mutation defineContractorAndManufactureAndAssociate { @@ -336,9 +328,9 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" mutation d1 { @@ -358,9 +350,9 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" mutation d2 { @@ -380,9 +372,9 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" mutation d3 { @@ -402,9 +394,9 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" mutation g1 { @@ -424,9 +416,9 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" mutation g2 { @@ -446,9 +438,9 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" mutation g3 { @@ -468,9 +460,9 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query a { @@ -551,27 +543,24 @@ mod test { } } "###); - } - - #[tokio::test] - async fn api_calls_resulting_in_no_data_changes_return_null() { - let (schema, _database) = test_schema().await; - - let from = Utc - .from_utc_datetime( - &NaiveDate::from_ymd_opt(1968, 9, 1) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - ) - .checked_add_signed(chronicle::chrono::Duration::days(1)) - .unwrap() - .to_rfc3339(); + } + + #[tokio::test] + async fn api_calls_resulting_in_no_data_changes_return_null() { + let (schema, _database) = test_schema().await; - let id_one = chronicle::async_graphql::Name::new("1"); - let id_two = chronicle::async_graphql::Name::new("2"); + let from = Utc + .from_utc_datetime( + &NaiveDate::from_ymd_opt(1968, 9, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(), + ) + .checked_add_signed(chronicle::chrono::Duration::days(1)) + .unwrap() + .to_rfc3339(); - insta::assert_json_snapshot!(schema + let id_one = chronicle::async_graphql::Name::new("1"); + let id_two = chronicle::async_graphql::Name::new("2"); + + insta::assert_json_snapshot!(schema .execute(Request::new( &format!( r#" @@ -755,7 +744,7 @@ mod test { } "###); - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( &format!( r#" @@ -927,7 +916,7 @@ mod test { } "###); - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( &format!( r#" @@ -1098,15 +1087,15 @@ mod test { } } "###); - } + } - #[tokio::test] - async fn one_of_id_or_external() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn one_of_id_or_external() { + let (schema, _database) = test_schema().await; - let external_id_input = chronicle::async_graphql::Name::new("withexternalid"); + let external_id_input = chronicle::async_graphql::Name::new("withexternalid"); - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( &format!( r#" @@ -1267,9 +1256,9 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( &format!( r#" @@ -1314,20 +1303,20 @@ mod test { } } "###); - } - - #[tokio::test] - async fn generic_json_object_attribute() { - let (schema, _database) = test_schema().await; - - // We doctor the test JSON input data as done in the `async_graphql` library JSON tests - // https://docs.rs/async-graphql/latest/src/async_graphql/types/json.rs.html#20. - // In fact, if you make the JSON input more complex, nesting data further, etc, it will cause - // "expected Name" errors. However, complex JSON inputs have been tested with success in the GraphQL - // Playground, as documented here: https://blockchaintp.atlassian.net/l/cp/0aocArV4 - let res = schema - .execute(Request::new( - r#" + } + + #[tokio::test] + async fn generic_json_object_attribute() { + let (schema, _database) = test_schema().await; + + // We doctor the test JSON input data as done in the `async_graphql` library JSON tests + // https://docs.rs/async-graphql/latest/src/async_graphql/types/json.rs.html#20. + // In fact, if you make the JSON input more complex, nesting data further, etc, it will + // cause "expected Name" errors. However, complex JSON inputs have been tested with success + // in the GraphQL Playground, as documented here: https://blockchaintp.atlassian.net/l/cp/0aocArV4 + let res = schema + .execute(Request::new( + r#" mutation { defineNCBAgent( externalId: "testagent2" @@ -1343,14 +1332,14 @@ mod test { } } "#, - )) - .await; + )) + .await; - assert_eq!(res.errors, vec![]); + assert_eq!(res.errors, vec![]); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query agent { @@ -1381,13 +1370,13 @@ mod test { } } "###); - } + } - #[tokio::test] - async fn agent_delegation() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn agent_delegation() { + let (schema, _database) = test_schema().await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1406,9 +1395,9 @@ mod test { context = 'chronicle:agent:testagent' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -1445,15 +1434,15 @@ mod test { } } "###); - } + } - #[tokio::test] - async fn agent_delegation_for_activity() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn agent_delegation_for_activity() { + let (schema, _database) = test_schema().await; - // create contractors + // create contractors - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1471,9 +1460,9 @@ mod test { context = 'chronicle:agent:huey' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1491,9 +1480,9 @@ mod test { context = 'chronicle:agent:dewey' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1511,11 +1500,11 @@ mod test { context = 'chronicle:agent:louie' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - // create activities + // create activities - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1533,9 +1522,9 @@ mod test { context = 'chronicle:activity:manufacture' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1553,11 +1542,11 @@ mod test { context = 'chronicle:activity:certification' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - // associate contractors with activities + // associate contractors with activities - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1576,9 +1565,9 @@ mod test { context = 'chronicle:agent:huey' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1597,9 +1586,9 @@ mod test { context = 'chronicle:agent:dewey' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1619,11 +1608,11 @@ mod test { context = 'chronicle:agent:huey' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - // check responsible and delegate are correct for activities + // check responsible and delegate are correct for activities - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -1675,7 +1664,7 @@ mod test { } "###); - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -1722,9 +1711,9 @@ mod test { } "###); - // use the same delegated contractor for two different roles + // use the same delegated contractor for two different roles - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1743,11 +1732,11 @@ mod test { context = 'chronicle:agent:huey' "###); - tokio::time::sleep(Duration::from_millis(1500)).await; + tokio::time::sleep(Duration::from_millis(1500)).await; - // check that same delegate is returned twice over + // check that same delegate is returned twice over - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -1812,13 +1801,13 @@ mod test { } } "###); - } + } - #[tokio::test] - async fn untyped_derivation() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn untyped_derivation() { + let (schema, _database) = test_schema().await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1834,9 +1823,9 @@ mod test { context = 'chronicle:entity:testentity1' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query { @@ -1862,13 +1851,13 @@ mod test { [[data.entityById.wasDerivedFrom]] id = 'chronicle:entity:testentity2' "###); - } + } - #[tokio::test] - async fn primary_source() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn primary_source() { + let (schema, _database) = test_schema().await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1884,9 +1873,9 @@ mod test { context = 'chronicle:entity:testentity1' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query { @@ -1921,13 +1910,13 @@ mod test { [[data.entityById.hadPrimarySource]] id = 'chronicle:entity:testentity2' "###); - } + } - #[tokio::test] - async fn revision() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn revision() { + let (schema, _database) = test_schema().await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -1943,9 +1932,9 @@ mod test { context = 'chronicle:entity:testentity1' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query { @@ -1979,13 +1968,13 @@ mod test { [[data.entityById.wasRevisionOf]] id = 'chronicle:entity:testentity2' "###); - } + } - #[tokio::test] - async fn quotation() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn quotation() { + let (schema, _database) = test_schema().await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -2001,9 +1990,9 @@ mod test { context = 'chronicle:entity:testentity1' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query { @@ -2037,13 +2026,13 @@ mod test { [[data.entityById.wasQuotedFrom]] id = 'chronicle:entity:testentity2' "###); - } + } - #[tokio::test] - async fn agent_can_be_created() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn agent_can_be_created() { + let (schema, _database) = test_schema().await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -2057,13 +2046,13 @@ mod test { [data.defineAgent] context = 'chronicle:agent:testentity1' "###); - } + } - #[tokio::test] - async fn agent_by_type() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn agent_by_type() { + let (schema, _database) = test_schema().await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -2078,9 +2067,9 @@ mod test { context = 'chronicle:agent:testagent1' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query { @@ -2097,13 +2086,13 @@ mod test { [[data.agentsByType.nodes]] id = 'chronicle:agent:testagent1' "###); - } + } - #[tokio::test] - async fn activity_by_type() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn activity_by_type() { + let (schema, _database) = test_schema().await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -2118,9 +2107,9 @@ mod test { context = 'chronicle:activity:testactivity1' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query { @@ -2137,13 +2126,13 @@ mod test { [[data.activitiesByType.nodes]] id = 'chronicle:activity:testactivity1' "###); - } + } - #[tokio::test] - async fn entity_by_type() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn entity_by_type() { + let (schema, _database) = test_schema().await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation { @@ -2158,11 +2147,11 @@ mod test { context = 'chronicle:entity:testentity1' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - let agent = schema - .execute(Request::new( - r#" + let agent = schema + .execute(Request::new( + r#" query { entitiesByType(entityType: CertificateEntity) { nodes { @@ -2172,21 +2161,21 @@ mod test { } } }"#, - )) - .await; + )) + .await; - insta::assert_toml_snapshot!(agent, @r###" + insta::assert_toml_snapshot!(agent, @r###" [[data.entitiesByType.nodes]] id = 'chronicle:entity:testentity1' "###); - } + } - #[tokio::test] - async fn was_informed_by() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn was_informed_by() { + let (schema, _database) = test_schema().await; - // create an activity - insta::assert_toml_snapshot!(schema + // create an activity + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation one { @@ -2201,8 +2190,8 @@ mod test { context = 'chronicle:activity:testactivityid1' "###); - // create another activity - insta::assert_toml_snapshot!(schema + // create another activity + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation two { @@ -2218,8 +2207,8 @@ mod test { context = 'chronicle:activity:testactivityid2' "###); - // establish WasInformedBy relationship - insta::assert_toml_snapshot!(schema + // establish WasInformedBy relationship + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation exec { @@ -2236,10 +2225,10 @@ mod test { context = 'chronicle:activity:testactivityid1' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // query WasInformedBy relationship - insta::assert_toml_snapshot!(schema + // query WasInformedBy relationship + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query test { @@ -2270,10 +2259,10 @@ mod test { externalId = 'testactivityid2' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // create a third activity - insta::assert_toml_snapshot!(schema + // create a third activity + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation three { @@ -2288,10 +2277,10 @@ mod test { context = 'chronicle:activity:testactivityid3' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // establish another WasInformedBy relationship - insta::assert_toml_snapshot!(schema + // establish another WasInformedBy relationship + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation execagain { @@ -2308,10 +2297,10 @@ mod test { context = 'chronicle:activity:testactivityid1' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // query WasInformedBy relationship - insta::assert_toml_snapshot!(schema + // query WasInformedBy relationship + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query test { @@ -2347,42 +2336,36 @@ mod test { id = 'chronicle:activity:testactivityid3' externalId = 'testactivityid3' "###); - } + } - #[tokio::test] - async fn was_attributed_to() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn was_attributed_to() { + let (schema, _database) = test_schema().await; - let test_activity = chronicle::async_graphql::Name::new("ItemCertified"); + let test_activity = chronicle::async_graphql::Name::new("ItemCertified"); - let from = Utc - .from_utc_datetime( - &NaiveDate::from_ymd_opt(2023, 3, 20) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - ) - .checked_add_signed(chronicle::chrono::Duration::days(1)) - .unwrap() - .to_rfc3339(); + let from = Utc + .from_utc_datetime( + &NaiveDate::from_ymd_opt(2023, 3, 20).unwrap().and_hms_opt(0, 0, 0).unwrap(), + ) + .checked_add_signed(chronicle::chrono::Duration::days(1)) + .unwrap() + .to_rfc3339(); - let test_entity = chronicle::async_graphql::Name::new("Certificate"); + let test_entity = chronicle::async_graphql::Name::new("Certificate"); - let test_agent = chronicle::async_graphql::Name::new("Certifier"); + let test_agent = chronicle::async_graphql::Name::new("Certifier"); - let to = Utc - .from_utc_datetime( - &NaiveDate::from_ymd_opt(2023, 3, 21) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - ) - .checked_add_signed(chronicle::chrono::Duration::days(1)) - .unwrap() - .to_rfc3339(); + let to = Utc + .from_utc_datetime( + &NaiveDate::from_ymd_opt(2023, 3, 21).unwrap().and_hms_opt(0, 0, 0).unwrap(), + ) + .checked_add_signed(chronicle::chrono::Duration::days(1)) + .unwrap() + .to_rfc3339(); - // create an activity that used an entity and was associated with an agent - insta::assert_json_snapshot!(schema + // create an activity that used an entity and was associated with an agent + insta::assert_json_snapshot!(schema .execute(Request::new( &format!( r#" @@ -2445,10 +2428,10 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // attribute the entity to the agent - insta::assert_json_snapshot!(schema + // attribute the entity to the agent + insta::assert_json_snapshot!(schema .execute(Request::new( &format!( r#" @@ -2469,10 +2452,10 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // query WasAttributedTo relationship - insta::assert_toml_snapshot!(schema + // query WasAttributedTo relationship + insta::assert_toml_snapshot!(schema .execute(Request::new( &format!( r#" @@ -2513,9 +2496,9 @@ mod test { locationAttribute = 'SomeLocation' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query queryWasAttributedToAnotherWay { @@ -2557,9 +2540,9 @@ mod test { locationAttribute = 'SomeLocation' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( &format!( r#" @@ -2600,9 +2583,9 @@ mod test { certIdAttribute = 'SomeCertId' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query queryAgentAttributionAnotherWay { @@ -2644,10 +2627,10 @@ mod test { certIdAttribute = 'SomeCertId' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // create another agent and attribute the entity to that other agent as well - insta::assert_json_snapshot!(schema + // create another agent and attribute the entity to that other agent as well + insta::assert_json_snapshot!(schema .execute(Request::new( &format!( r#" @@ -2674,10 +2657,10 @@ mod test { } "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // query WasAttributedTo relationship - insta::assert_toml_snapshot!(schema + // query WasAttributedTo relationship + insta::assert_toml_snapshot!(schema .execute(Request::new( &format!( r#" @@ -2727,9 +2710,9 @@ mod test { locationAttribute = 'AnotherLocation' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query queryWasAttributedToSecondAgentAnotherWay { @@ -2780,9 +2763,9 @@ mod test { locationAttribute = 'AnotherLocation' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query querySecondAgentAttribution { @@ -2822,9 +2805,9 @@ mod test { certIdAttribute = 'SomeCertId' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - insta::assert_toml_snapshot!(schema + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query querySecondAgentAttributionAnotherWay { @@ -2878,14 +2861,14 @@ mod test { id = 'chronicle:entity:Certificate' certIdAttribute = 'SomeCertId' "###); - } + } - #[tokio::test] - async fn generated() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn generated() { + let (schema, _database) = test_schema().await; - // create an activity - insta::assert_toml_snapshot!(schema + // create an activity + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation activity { @@ -2900,8 +2883,8 @@ mod test { context = 'chronicle:activity:testactivity1' "###); - // create an entity - insta::assert_toml_snapshot!(schema + // create an entity + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation entity { @@ -2916,8 +2899,8 @@ mod test { context = 'chronicle:entity:testentity1' "###); - // establish Generated relationship - insta::assert_toml_snapshot!(schema + // establish Generated relationship + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation generated { @@ -2934,10 +2917,10 @@ mod test { context = 'chronicle:entity:testentity1' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // query Generated relationship - insta::assert_toml_snapshot!(schema + // query Generated relationship + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query test { @@ -2968,11 +2951,11 @@ mod test { externalId = 'testentity1' "###); - // The following demonstrates that a second wasGeneratedBy - // relationship cannot be made once the first has been established. + // The following demonstrates that a second wasGeneratedBy + // relationship cannot be made once the first has been established. - // create another entity - insta::assert_toml_snapshot!(schema + // create another entity + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation second { @@ -2988,8 +2971,8 @@ mod test { context = 'chronicle:entity:testitem' "###); - // establish another Generated relationship - insta::assert_toml_snapshot!(schema + // establish another Generated relationship + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" mutation again { @@ -3006,10 +2989,10 @@ mod test { context = 'chronicle:entity:testitem' "###); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // query Generated relationship - insta::assert_toml_snapshot!(schema + // query Generated relationship + insta::assert_toml_snapshot!(schema .execute(Request::new( r#" query testagain { @@ -3043,13 +3026,13 @@ mod test { id = 'chronicle:entity:testentity1' externalId = 'testentity1' "###); - } + } - #[tokio::test] - async fn query_activity_timeline() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn query_activity_timeline() { + let (schema, _database) = test_schema().await; - let res = schema + let res = schema .execute(Request::new( r#" mutation { @@ -3061,13 +3044,13 @@ mod test { )) .await; - assert_eq!(res.errors, vec![]); + assert_eq!(res.errors, vec![]); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - let res = schema - .execute(Request::new( - r#" + let res = schema + .execute(Request::new( + r#" mutation { defineNCBAgent( externalId: "testagent2" @@ -3080,14 +3063,14 @@ mod test { } } "#, - )) - .await; + )) + .await; - assert_eq!(res.errors, vec![]); + assert_eq!(res.errors, vec![]); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - let res = schema + let res = schema .execute(Request::new( r#" mutation { @@ -3099,42 +3082,39 @@ mod test { )) .await; - assert_eq!(res.errors, vec![]); + assert_eq!(res.errors, vec![]); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - let res = schema - .execute(Request::new( - r#" + let res = schema + .execute(Request::new( + r#" mutation { defineNCBRecordEntity(externalId:"testentity2") { context } } "#, - )) - .await; + )) + .await; - assert_eq!(res.errors, vec![]); + assert_eq!(res.errors, vec![]); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - let from = Utc.from_utc_datetime( - &NaiveDate::from_ymd_opt(1968, 9, 1) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - ); + let from = Utc.from_utc_datetime( + &NaiveDate::from_ymd_opt(1968, 9, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(), + ); - for i in 1..10 { - let activity_name = if i % 2 == 0 { - format!("testactivity{i}") - } else { - format!("anothertestactivity{i}") - }; + for i in 1..10 { + let activity_name = if i % 2 == 0 { + format!("testactivity{i}") + } else { + format!("anothertestactivity{i}") + }; - if (i % 2) == 0 { - let res = schema + if (i % 2) == 0 { + let res = schema .execute(Request::new( &format!( r#" @@ -3148,25 +3128,25 @@ mod test { )) .await; - assert_eq!(res.errors, vec![]); - } else { - let res = schema - .execute(Request::new(&format!( - r#" + assert_eq!(res.errors, vec![]); + } else { + let res = schema + .execute(Request::new(&format!( + r#" mutation {{ defineItemCodifiedActivity(externalId:"{activity_name}") {{ context }} }} "# - ))) - .await; + ))) + .await; - assert_eq!(res.errors, vec![]); - } + assert_eq!(res.errors, vec![]); + } - tokio::time::sleep(Duration::from_millis(1000)).await; - let res = schema + tokio::time::sleep(Duration::from_millis(1000)).await; + let res = schema .execute(Request::new(format!( r#" mutation {{ @@ -3177,55 +3157,51 @@ mod test { "# ))) .await; - assert_eq!(res.errors, vec![]); + assert_eq!(res.errors, vec![]); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - let res = schema - .execute(Request::new(format!( - r#" + let res = schema + .execute(Request::new(format!( + r#" mutation {{ startActivity( time: "{}", id: {{id: "chronicle:activity:{}" }}) {{ context }} }} "#, - from.checked_add_signed(chronicle::chrono::Duration::days(i)) - .unwrap() - .to_rfc3339(), - activity_name - ))) - .await; - - assert_eq!(res.errors, vec![]); - - let res = schema - .execute(Request::new(format!( - r#" + from.checked_add_signed(chronicle::chrono::Duration::days(i)) + .unwrap() + .to_rfc3339(), + activity_name + ))) + .await; + + assert_eq!(res.errors, vec![]); + + let res = schema + .execute(Request::new(format!( + r#" mutation {{ endActivity( time: "{}", id: {{ id: "chronicle:activity:{}" }}) {{ context }} }} "#, - from.checked_add_signed(chronicle::chrono::Duration::days(i)) - .unwrap() - .to_rfc3339(), - activity_name - ))) - .await; + from.checked_add_signed(chronicle::chrono::Duration::days(i)) + .unwrap() + .to_rfc3339(), + activity_name + ))) + .await; - assert_eq!(res.errors, vec![]); + assert_eq!(res.errors, vec![]); - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - let agent = if i % 2 == 0 { - "testagent1" - } else { - "testagent2" - }; + let agent = if i % 2 == 0 { "testagent1" } else { "testagent2" }; - let res = schema + let res = schema .execute(Request::new(format!( r#" mutation {{ @@ -3237,15 +3213,15 @@ mod test { ))) .await; - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - assert_eq!(res.errors, vec![]); - } + assert_eq!(res.errors, vec![]); + } - tokio::time::sleep(Duration::from_millis(3000)).await; + tokio::time::sleep(Duration::from_millis(3000)).await; - // Entire timeline in order - insta::assert_json_snapshot!(schema + // Entire timeline in order + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -3615,8 +3591,8 @@ mod test { } "###); - // Entire timeline reverse order - insta::assert_json_snapshot!(schema + // Entire timeline reverse order + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -3986,12 +3962,12 @@ mod test { } "###); - // By activity type + // By activity type - // Note the case of `ItemCertified` and `ItemCodified` in the `activityTypes` - // field of the query here, as it is not standard GraphQL but is tailored to - // meet client requirements of preserving domain case inflections. - insta::assert_json_snapshot!(schema + // Note the case of `ItemCertified` and `ItemCodified` in the `activityTypes` + // field of the query here, as it is not standard GraphQL but is tailored to + // meet client requirements of preserving domain case inflections. + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -4088,8 +4064,8 @@ mod test { } "###); - // As previous but omitting forEntity and forAgent - insta::assert_json_snapshot!(schema + // As previous but omitting forEntity and forAgent + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -4184,8 +4160,8 @@ mod test { } "###); - // By agent - insta::assert_json_snapshot!(schema + // By agent + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -4258,8 +4234,8 @@ mod test { } "###); - // As previous but omitting forEntity and activityTypes - insta::assert_json_snapshot!(schema + // As previous but omitting forEntity and activityTypes + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -4329,14 +4305,14 @@ mod test { } } "###); - } + } - #[tokio::test] - async fn query_agents_by_cursor() { - let (schema, _database) = test_schema().await; + #[tokio::test] + async fn query_agents_by_cursor() { + let (schema, _database) = test_schema().await; - for i in 0..100 { - let res = schema + for i in 0..100 { + let res = schema .execute(Request::new(format!( r#" mutation {{ @@ -4348,17 +4324,17 @@ mod test { ))) .await; - assert_eq!(res.errors, vec![]); - } + assert_eq!(res.errors, vec![]); + } - tokio::time::sleep(Duration::from_millis(1000)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; - // Default cursor + // Default cursor - // Note the case of `Contractor` in the `agentsByType(agentType:` field of - // the query here, as it is not standard GraphQL but is tailored to meet - // client requirements of preserving domain case inflections. - insta::assert_json_snapshot!(schema + // Note the case of `Contractor` in the `agentsByType(agentType:` field of + // the query here, as it is not standard GraphQL but is tailored to meet + // client requirements of preserving domain case inflections. + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -4491,12 +4467,12 @@ mod test { } "###); - // Middle cursor + // Middle cursor - // Note the case of `Contractor` in the `agentsByType(agentType:` field of - // the query here, as it is not standard GraphQL but is tailored to meet - // client requirements of preserving domain case inflections. - insta::assert_json_snapshot!(schema + // Note the case of `Contractor` in the `agentsByType(agentType:` field of + // the query here, as it is not standard GraphQL but is tailored to meet + // client requirements of preserving domain case inflections. + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -4719,12 +4695,12 @@ mod test { } "###); - // Out of bound cursor + // Out of bound cursor - // Note the case of `Contractor` in the `agentsByType(agentType:` field of - // the query here, as it is not standard GraphQL but is tailored to meet - // client requirements of preserving domain case inflections. - insta::assert_json_snapshot!(schema + // Note the case of `Contractor` in the `agentsByType(agentType:` field of + // the query here, as it is not standard GraphQL but is tailored to meet + // client requirements of preserving domain case inflections. + insta::assert_json_snapshot!(schema .execute(Request::new( r#" query { @@ -4847,26 +4823,26 @@ mod test { } } "###); - } + } - #[tokio::test] - async fn subscribe_commit_notification() { - use chronicle::async_graphql::futures_util::StreamExt; + #[tokio::test] + async fn subscribe_commit_notification() { + use chronicle::async_graphql::futures_util::StreamExt; - let (schema, _database) = test_schema().await; + let (schema, _database) = test_schema().await; - let mut stream = schema.execute_stream(Request::new( - r#" + let mut stream = schema.execute_stream(Request::new( + r#" subscription { commitNotifications { stage } } "# - .to_string(), - )); + .to_string(), + )); - insta::assert_json_snapshot!(schema + insta::assert_json_snapshot!(schema .execute(Request::new( r#" mutation { @@ -4889,9 +4865,9 @@ mod test { } "#); - let res = stream.next().await.unwrap(); + let res = stream.next().await.unwrap(); - insta::assert_json_snapshot!(res, @r###" + insta::assert_json_snapshot!(res, @r###" { "data": { "commitNotifications": { @@ -4900,84 +4876,80 @@ mod test { } } "###); - } - - async fn subscription_response( - schema: &Schema, - subscription: &str, - mutation: &str, - ) -> Response { - use futures::StreamExt; - - let mut stream = schema.execute_stream(Request::new(subscription)); - assert!(schema.execute(Request::new(mutation)).await.is_ok()); - stream.next().await.unwrap() - } - - struct SchemaPair<'a> { - schema_allow: Schema, - schema_deny: Schema, - _databases: (TemporaryDatabase<'a>, TemporaryDatabase<'a>), - } - - impl<'a> SchemaPair<'a> { - fn new( - (schema_allow, database_allow): ( - Schema, - TemporaryDatabase<'a>, - ), - (schema_deny, database_deny): ( - Schema, - TemporaryDatabase<'a>, - ), - ) -> Self { - Self { - schema_allow, - schema_deny, - _databases: (database_allow, database_deny), - } - } - - async fn check_responses( - res_allow: impl Future, - res_deny: impl Future, - ) { - use chronicle::async_graphql::Value; - - let res_allow = res_allow.await; - let res_deny = res_deny.await; - - assert_ne!(res_allow.data, Value::Null); - assert!(res_allow.errors.is_empty()); - - assert_eq!(res_deny.data, Value::Null); - assert!(!res_deny.errors.is_empty()); - } - - async fn check_responses_qm(&self, query: &str) { - Self::check_responses( - self.schema_allow.execute(Request::new(query)), - self.schema_deny.execute(Request::new(query)), - ) - .await; - } - - async fn check_responses_s(&self, subscription: &str, mutation: &str) { - Self::check_responses( - subscription_response(&self.schema_allow, subscription, mutation), - subscription_response(&self.schema_deny, subscription, mutation), - ) - .await; - } - } - - #[tokio::test] - async fn query_api_secured() { - let schemas = SchemaPair::new(test_schema().await, test_schema_blocked_api().await); - - schemas - .check_responses_qm( - r#" + } + + async fn subscription_response( + schema: &Schema, + subscription: &str, + mutation: &str, + ) -> Response { + use futures::StreamExt; + + let mut stream = schema.execute_stream(Request::new(subscription)); + assert!(schema.execute(Request::new(mutation)).await.is_ok()); + stream.next().await.unwrap() + } + + struct SchemaPair<'a> { + schema_allow: Schema, + schema_deny: Schema, + _databases: (TemporaryDatabase<'a>, TemporaryDatabase<'a>), + } + + impl<'a> SchemaPair<'a> { + fn new( + (schema_allow, database_allow): ( + Schema, + TemporaryDatabase<'a>, + ), + (schema_deny, database_deny): ( + Schema, + TemporaryDatabase<'a>, + ), + ) -> Self { + Self { schema_allow, schema_deny, _databases: (database_allow, database_deny) } + } + + async fn check_responses( + res_allow: impl Future, + res_deny: impl Future, + ) { + use chronicle::async_graphql::Value; + + let res_allow = res_allow.await; + let res_deny = res_deny.await; + + assert_ne!(res_allow.data, Value::Null); + assert!(res_allow.errors.is_empty()); + + assert_eq!(res_deny.data, Value::Null); + assert!(!res_deny.errors.is_empty()); + } + + async fn check_responses_qm(&self, query: &str) { + Self::check_responses( + self.schema_allow.execute(Request::new(query)), + self.schema_deny.execute(Request::new(query)), + ) + .await; + } + + async fn check_responses_s(&self, subscription: &str, mutation: &str) { + Self::check_responses( + subscription_response(&self.schema_allow, subscription, mutation), + subscription_response(&self.schema_deny, subscription, mutation), + ) + .await; + } + } + + #[tokio::test] + async fn query_api_secured() { + let schemas = SchemaPair::new(test_schema().await, test_schema_blocked_api().await); + + schemas + .check_responses_qm( + r#" query { activityTimeline( activityTypes: [], @@ -4989,12 +4961,12 @@ mod test { } } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" query { agentsByType( agentType: ContractorAgent @@ -5006,12 +4978,12 @@ mod test { } } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" query { activitiesByType( activityType: ItemCertifiedActivity @@ -5023,12 +4995,12 @@ mod test { } } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" query { entitiesByType( entityType: CertificateEntity @@ -5040,12 +5012,12 @@ mod test { } } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" query { agentById(id: { id: "chronicle:agent:testagent" }) { ... on ProvAgent { @@ -5053,12 +5025,12 @@ mod test { } } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" query { activityById(id: { id: "chronicle:activity:testactivity" }) { ... on ProvActivity { @@ -5066,12 +5038,12 @@ mod test { } } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" query { entityById(id: { id: "chronicle:entity:testentity" }) { ... on ProvEntity { @@ -5079,30 +5051,30 @@ mod test { } } }"#, - ) - .await; - } - - #[tokio::test] - async fn subscribe_api_secured() { - let loader = CliPolicyLoader::from_embedded_policy( - "allow_transactions", - "allow_transactions.allow_defines", - ) - .unwrap(); - let opa_executor = ExecutorContext::from_loader(&loader).unwrap(); - let test_schema_allow_defines = test_schema_with_opa(opa_executor).await; - let schemas = SchemaPair::new(test_schema().await, test_schema_allow_defines); - - schemas - .check_responses_s( - r#" + ) + .await; + } + + #[tokio::test] + async fn subscribe_api_secured() { + let loader = CliPolicyLoader::from_embedded_policy( + "allow_transactions", + "allow_transactions.allow_defines", + ) + .unwrap(); + let opa_executor = ExecutorContext::from_loader(&loader).unwrap(); + let test_schema_allow_defines = test_schema_with_opa(opa_executor).await; + let schemas = SchemaPair::new(test_schema().await, test_schema_allow_defines); + + schemas + .check_responses_s( + r#" subscription { commitNotifications { stage } }"#, - r#" + r#" mutation { defineContractorAgent( externalId: "testagent" @@ -5111,53 +5083,53 @@ mod test { context } }"#, - ) - .await; - } + ) + .await; + } - #[tokio::test] - async fn mutation_api_secured() { - let schemas = SchemaPair::new(test_schema().await, test_schema_blocked_api().await); + #[tokio::test] + async fn mutation_api_secured() { + let schemas = SchemaPair::new(test_schema().await, test_schema_blocked_api().await); - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { defineAgent( externalId: "test agent", attributes: {} ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { defineActivity( externalId: "test activity", attributes: {} ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { defineEntity( externalId: "test entity", attributes: {} ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { actedOnBehalfOf( responsible: { externalId: "test agent 1" }, @@ -5165,36 +5137,36 @@ mod test { role: MANUFACTURER ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { wasDerivedFrom( generatedEntity: { externalId: "test entity 1" }, usedEntity: { externalId: "test entity 2" } ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { wasRevisionOf( generatedEntity: { externalId: "test entity 1" }, usedEntity: { externalId: "test entity 2" } ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { hadPrimarySource( generatedEntity: { externalId: "test entity 1" }, @@ -5202,57 +5174,57 @@ mod test { ) { context } } "#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { wasQuotedFrom( generatedEntity: { externalId: "test entity 1" }, usedEntity: { externalId: "test entity 2" } ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { instantActivity( id: { externalId: "test activity 1" } ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { startActivity( id: { externalId: "test activity 2" } ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { endActivity( id: { externalId: "test activity 2" } ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { wasAssociatedWith( responsible: { externalId: "test agent" }, @@ -5260,84 +5232,84 @@ mod test { role: MANUFACTURER ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { used( activity: { externalId: "test activity" }, id: { externalId: "test entity" } ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { wasInformedBy( activity: { externalId: "test activity 1" }, informingActivity: { externalId: "test activity 2" } ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { wasGeneratedBy( activity: { externalId: "test activity" }, id: { externalId: "test entity" } ) { context } }"#, - ) - .await; - } + ) + .await; + } - #[tokio::test] - async fn mutation_generated_api_secured() { - let schemas = SchemaPair::new(test_schema().await, test_schema_blocked_api().await); + #[tokio::test] + async fn mutation_generated_api_secured() { + let schemas = SchemaPair::new(test_schema().await, test_schema_blocked_api().await); - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { defineContractorAgent( externalId: "test agent" attributes: { locationAttribute: "location" } ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { defineItemCertifiedActivity( externalId: "test activity", attributes: { certIdAttribute: "12345" } ) { context } }"#, - ) - .await; + ) + .await; - schemas - .check_responses_qm( - r#" + schemas + .check_responses_qm( + r#" mutation { defineCertificateEntity( externalId: "test entity", attributes: { certIdAttribute: "12345" } ) { context } }"#, - ) - .await; - } + ) + .await; + } } diff --git a/crates/chronicle-domain/build.rs b/crates/chronicle-domain/build.rs index 5a6048d20..d169ebf68 100644 --- a/crates/chronicle-domain/build.rs +++ b/crates/chronicle-domain/build.rs @@ -1,26 +1,26 @@ use std::process::Command; use chronicle::{ - codegen::{linter, ChronicleDomainDef}, - generate_chronicle_domain_schema, + codegen::{linter, ChronicleDomainDef}, + generate_chronicle_domain_schema, }; fn main() { - println!("cargo:rerun-if-changed=domain.yaml"); - linter::check_files(vec!["domain.yaml"]); - let model = ChronicleDomainDef::from_file("domain.yaml").unwrap(); + println!("cargo:rerun-if-changed=domain.yaml"); + linter::check_files(vec!["domain.yaml"]); + let model = ChronicleDomainDef::from_file("domain.yaml").unwrap(); - generate_chronicle_domain_schema(model, "src/main.rs"); + generate_chronicle_domain_schema(model, "src/main.rs"); - Command::new("cargo") - .args(["fmt", "--", "src/main.rs"]) - .output() - .expect("formatting"); + Command::new("cargo") + .args(["fmt", "--", "src/main.rs"]) + .output() + .expect("formatting"); - //Create a .VERSION file containing 'local' if it does not exist + //Create a .VERSION file containing 'local' if it does not exist - let version_file = std::path::Path::new("../../.VERSION"); - if !version_file.exists() { - std::fs::write(version_file, "local").expect("Unable to write file"); - } + let version_file = std::path::Path::new("../../.VERSION"); + if !version_file.exists() { + std::fs::write(version_file, "local").expect("Unable to write file"); + } } diff --git a/crates/chronicle-protocol/build.rs b/crates/chronicle-protocol/build.rs index 48959a6fc..109764ea7 100644 --- a/crates/chronicle-protocol/build.rs +++ b/crates/chronicle-protocol/build.rs @@ -1,10 +1,10 @@ use std::io::Result; fn main() -> Result<()> { - let protos = glob::glob("./src/protos/*.proto") - .unwrap() - .map(|x| x.unwrap()) - .collect::>(); - prost_build::compile_protos(&protos, &["./src/protos"])?; - Ok(()) + let protos = glob::glob("./src/protos/*.proto") + .unwrap() + .map(|x| x.unwrap()) + .collect::>(); + prost_build::compile_protos(&protos, &["./src/protos"])?; + Ok(()) } diff --git a/crates/chronicle-protocol/src/address.rs b/crates/chronicle-protocol/src/address.rs index 562a5baa0..59e43701a 100644 --- a/crates/chronicle-protocol/src/address.rs +++ b/crates/chronicle-protocol/src/address.rs @@ -1,18 +1,18 @@ -use std::fmt::Display; +use core::fmt::Display; use common::{ - ledger::{LedgerAddress, NameSpacePart, ResourcePart}, - prov::AsCompact, + ledger::{LedgerAddress, NameSpacePart, ResourcePart}, + prov::AsCompact, }; use lazy_static::lazy_static; use openssl::sha::Sha256; lazy_static! { - pub static ref PREFIX: String = { - let mut sha = Sha256::new(); - sha.update("chronicle".as_bytes()); - hex::encode(sha.finish())[..6].to_string() - }; + pub static ref PREFIX: String = { + let mut sha = Sha256::new(); + sha.update("chronicle".as_bytes()); + hex::encode(sha.finish())[..6].to_string() + }; } pub static VERSION: &str = "1.0"; @@ -22,26 +22,26 @@ pub static FAMILY: &str = "chronicle"; pub struct SawtoothAddress(String); impl SawtoothAddress { - pub fn new(address: String) -> Self { - SawtoothAddress(address) - } + pub fn new(address: String) -> Self { + SawtoothAddress(address) + } } /// Our sawtooth addresses use hash(chronicle)[..6] as the prefix, /// followed by a 256 bit hash of the resource Iri and namespace Iri. impl From<&LedgerAddress> for SawtoothAddress { - fn from(addr: &LedgerAddress) -> Self { - let mut sha = Sha256::new(); - if let Some(ns) = addr.namespace_part().as_ref() { - sha.update(ns.compact().as_bytes()) - } - sha.update(addr.resource_part().compact().as_bytes()); - SawtoothAddress(format!("{}{}", &*PREFIX, hex::encode(sha.finish()))) - } + fn from(addr: &LedgerAddress) -> Self { + let mut sha = Sha256::new(); + if let Some(ns) = addr.namespace_part().as_ref() { + sha.update(ns.compact().as_bytes()) + } + sha.update(addr.resource_part().compact().as_bytes()); + SawtoothAddress(format!("{}{}", &*PREFIX, hex::encode(sha.finish()))) + } } impl Display for SawtoothAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(&self.0) + } } diff --git a/crates/chronicle-protocol/src/lib.rs b/crates/chronicle-protocol/src/lib.rs index 432a22c8f..d3a550778 100644 --- a/crates/chronicle-protocol/src/lib.rs +++ b/crates/chronicle-protocol/src/lib.rs @@ -1,6 +1,6 @@ use async_stl_client::{ - ledger::SawtoothLedger, - zmq_client::{RetryingRequestResponseChannel, ZmqRequestResponseSawtoothChannel}, + ledger::SawtoothLedger, + zmq_client::{RetryingRequestResponseChannel, ZmqRequestResponseSawtoothChannel}, }; use messages::ChronicleSubmitTransaction; @@ -16,13 +16,13 @@ static PROTOCOL_VERSION: &str = "2"; const SUBMISSION_BODY_VERSION: u16 = 1; pub type ChronicleLedger = SawtoothLedger< - RetryingRequestResponseChannel, - ChronicleOperationEvent, - ChronicleSubmitTransaction, + RetryingRequestResponseChannel, + ChronicleOperationEvent, + ChronicleSubmitTransaction, >; pub mod sawtooth { - #![allow(clippy::derive_partial_eq_without_eq)] + #![allow(clippy::derive_partial_eq_without_eq)] - include!(concat!(env!("OUT_DIR"), "/_.rs")); + include!(concat!(env!("OUT_DIR"), "/_.rs")); } diff --git a/crates/chronicle-protocol/src/messages.rs b/crates/chronicle-protocol/src/messages.rs index 102ab05da..e48ff1aed 100644 --- a/crates/chronicle-protocol/src/messages.rs +++ b/crates/chronicle-protocol/src/messages.rs @@ -7,134 +7,129 @@ use opa_tp_protocol::state::{policy_address, policy_meta_address}; use serde_json::json; use crate::{ - address::SawtoothAddress, - protocol::ProtocolError, - sawtooth::submission::{BodyVariant, IdentityVariant}, - settings::sawtooth_settings_address, - PROTOCOL_VERSION, SUBMISSION_BODY_VERSION, + address::SawtoothAddress, + protocol::ProtocolError, + sawtooth::submission::{BodyVariant, IdentityVariant}, + settings::sawtooth_settings_address, + PROTOCOL_VERSION, SUBMISSION_BODY_VERSION, }; use super::sawtooth::*; use async_stl_client::{ - ledger::{LedgerTransaction, TransactionId}, - sawtooth::{MessageBuilder, TransactionPayload}, + ledger::{LedgerTransaction, TransactionId}, + sawtooth::{MessageBuilder, TransactionPayload}, }; use prost::Message; #[derive(Debug, Clone)] pub struct ChronicleSubmitTransaction { - pub tx: ChronicleTransaction, - pub signer: ChronicleSigning, - pub policy_name: Option, + pub tx: ChronicleTransaction, + pub signer: ChronicleSigning, + pub policy_name: Option, } #[async_trait::async_trait] impl TransactionPayload for ChronicleSubmitTransaction { - type Error = ProtocolError; - - /// Envelope a payload of `ChronicleOperations` and `SignedIdentity` in a `Submission` protocol buffer, - /// along with placeholders for protocol version info and a tracing span id. - async fn to_bytes(&self) -> Result, ProtocolError> { - let mut submission = Submission { - version: PROTOCOL_VERSION.to_string(), - span_id: 0u64, - ..Default::default() - }; - - let mut ops = Vec::with_capacity(self.tx.tx.len()); - for op in &self.tx.tx { - let op_json = op.to_json(); - let compact_json = op_json.compact_stable_order().await?; - ops.push(compact_json); - } - - let ops_json = - serde_json::to_string(&json!({"version": SUBMISSION_BODY_VERSION, "ops": ops}))?; - let identity_json = serde_json::to_string(&self.tx.identity)?; - tracing::debug!(ops_json = %ops_json, identity_json = %identity_json); - - submission.body_variant = Some(BodyVariant::Body(BodyMessageV1 { payload: ops_json })); - submission.identity_variant = Some(IdentityVariant::Identity(IdentityMessageV1 { - payload: identity_json, - })); - Ok(submission.encode_to_vec()) - } + type Error = ProtocolError; + + /// Envelope a payload of `ChronicleOperations` and `SignedIdentity` in a `Submission` protocol + /// buffer, along with placeholders for protocol version info and a tracing span id. + async fn to_bytes(&self) -> Result, ProtocolError> { + let mut submission = Submission { + version: PROTOCOL_VERSION.to_string(), + span_id: 0u64, + ..Default::default() + }; + + let mut ops = Vec::with_capacity(self.tx.tx.len()); + for op in &self.tx.tx { + let op_json = op.to_json(); + let compact_json = op_json.compact_stable_order().await?; + ops.push(compact_json); + } + + let ops_json = + serde_json::to_string(&json!({"version": SUBMISSION_BODY_VERSION, "ops": ops}))?; + let identity_json = serde_json::to_string(&self.tx.identity)?; + tracing::debug!(ops_json = %ops_json, identity_json = %identity_json); + + submission.body_variant = Some(BodyVariant::Body(BodyMessageV1 { payload: ops_json })); + submission.identity_variant = + Some(IdentityVariant::Identity(IdentityMessageV1 { payload: identity_json })); + Ok(submission.encode_to_vec()) + } } impl ChronicleSubmitTransaction { - pub fn new( - tx: ChronicleTransaction, - signer: ChronicleSigning, - policy_name: Option, - ) -> Self { - Self { - tx, - signer, - policy_name, - } - } + pub fn new( + tx: ChronicleTransaction, + signer: ChronicleSigning, + policy_name: Option, + ) -> Self { + Self { tx, signer, policy_name } + } } #[async_trait::async_trait] impl LedgerTransaction for ChronicleSubmitTransaction { - type Error = SecretError; - - async fn sign(&self, bytes: Arc>) -> Result, SecretError> { - self.signer.batcher_sign(&bytes).await - } - - async fn verifying_key(&self) -> Result { - self.signer.batcher_verifying().await - } - - fn addresses(&self) -> Vec { - self.tx - .tx - .iter() - .flat_map(|op| op.dependencies()) - .map(|dep| SawtoothAddress::from(&dep).to_string()) - .collect::>() - .into_iter() - .collect() - } - - async fn as_sawtooth_tx( - &self, - message_builder: &MessageBuilder, - ) -> Result<(async_stl_client::messages::Transaction, TransactionId), Self::Error> { - //Ensure we append any opa policy binary address and meta address to the - //list of addresses, along with the settings address - let mut addresses: Vec<_> = self - .addresses() - .into_iter() - .chain(vec![ - sawtooth_settings_address("chronicle.opa.policy_name"), - sawtooth_settings_address("chronicle.opa.entrypoint"), - ]) - .collect(); - - if self.policy_name.is_some() { - addresses = addresses - .into_iter() - .chain(vec![ - policy_address(self.policy_name.as_ref().unwrap()), - policy_meta_address(self.policy_name.as_ref().unwrap()), - ]) - .collect(); - } - message_builder - .make_sawtooth_transaction( - addresses.clone(), - addresses, - vec![], - self, - self.signer.batcher_verifying().await?, - |bytes| { - let signer = self.signer.clone(); - let bytes = bytes.to_vec(); - async move { signer.batcher_sign(&bytes).await } - }, - ) - .await - } + type Error = SecretError; + + async fn sign(&self, bytes: Arc>) -> Result, SecretError> { + self.signer.batcher_sign(&bytes).await + } + + async fn verifying_key(&self) -> Result { + self.signer.batcher_verifying().await + } + + fn addresses(&self) -> Vec { + self.tx + .tx + .iter() + .flat_map(|op| op.dependencies()) + .map(|dep| SawtoothAddress::from(&dep).to_string()) + .collect::>() + .into_iter() + .collect() + } + + async fn as_sawtooth_tx( + &self, + message_builder: &MessageBuilder, + ) -> Result<(async_stl_client::messages::Transaction, TransactionId), Self::Error> { + //Ensure we append any opa policy binary address and meta address to the + //list of addresses, along with the settings address + let mut addresses: Vec<_> = self + .addresses() + .into_iter() + .chain(vec![ + sawtooth_settings_address("chronicle.opa.policy_name"), + sawtooth_settings_address("chronicle.opa.entrypoint"), + ]) + .collect(); + + if self.policy_name.is_some() { + addresses = addresses + .into_iter() + .chain(vec![ + policy_address(self.policy_name.as_ref().unwrap()), + policy_meta_address(self.policy_name.as_ref().unwrap()), + ]) + .collect(); + } + message_builder + .make_sawtooth_transaction( + addresses.clone(), + addresses, + vec![], + self, + self.signer.batcher_verifying().await?, + |bytes| { + let signer = self.signer.clone(); + let bytes = bytes.to_vec(); + async move { signer.batcher_sign(&bytes).await } + }, + ) + .await + } } diff --git a/crates/chronicle-protocol/src/protocol.rs b/crates/chronicle-protocol/src/protocol.rs index 18b257589..3384ba077 100644 --- a/crates/chronicle-protocol/src/protocol.rs +++ b/crates/chronicle-protocol/src/protocol.rs @@ -1,15 +1,15 @@ use std::io::Cursor; use async_stl_client::{ - error::SawtoothCommunicationError, - ledger::{LedgerEvent, Span}, + error::SawtoothCommunicationError, + ledger::{LedgerEvent, Span}, }; use common::{ - identity::SignedIdentity, - prov::{ - operations::ChronicleOperation, to_json_ld::ToJson, CompactionError, Contradiction, - PayloadError, ProcessorError, ProvModel, - }, + identity::SignedIdentity, + prov::{ + operations::ChronicleOperation, to_json_ld::ToJson, CompactionError, Contradiction, + PayloadError, ProcessorError, ProvModel, + }, }; use prost::Message; use tracing::span; @@ -22,85 +22,84 @@ use self::messages::event::OptionContradiction; pub struct ChronicleOperationEvent(pub Result, pub SignedIdentity); impl From for Result { - fn from(val: ChronicleOperationEvent) -> Self { - val.0 - } + fn from(val: ChronicleOperationEvent) -> Self { + val.0 + } } #[async_trait::async_trait] impl LedgerEvent for ChronicleOperationEvent { - async fn deserialize( - buf: &[u8], - ) -> Result<(Self, Span), async_stl_client::error::SawtoothCommunicationError> - where - Self: Sized, - { - let event = messages::Event::decode(buf) - .map_err(|e| SawtoothCommunicationError::LedgerEventParse { source: e.into() })?; - // Spans of zero panic, so assign a dummy value until we thread the span correctly - let span_id = { - if event.span_id == 0 { - span::Id::from_u64(0xffffffffffffffff) - } else { - span::Id::from_u64(event.span_id) - } - }; - let model = match (event.delta, event.option_contradiction) { - (_, Some(OptionContradiction::Contradiction(contradiction))) => Err( - serde_json::from_str::(&contradiction).map_err(|e| { - SawtoothCommunicationError::LedgerEventParse { source: e.into() } - })?, - ), - (delta, None) => { - let mut model = ProvModel::default(); - model.apply_json_ld_str(&delta).await.map_err(|e| { - SawtoothCommunicationError::LedgerEventParse { source: e.into() } - })?; - - Ok(model) - } - }; - - let identity = { - if event.identity.is_empty() { - SignedIdentity::new_no_identity() - } else { - serde_json::from_str(&event.identity).map_err(|e| { - SawtoothCommunicationError::LedgerEventParse { source: e.into() } - })? - } - }; - Ok((Self(model, identity), Span::Span(span_id.into_u64()))) - } + async fn deserialize( + buf: &[u8], + ) -> Result<(Self, Span), async_stl_client::error::SawtoothCommunicationError> + where + Self: Sized, + { + let event = messages::Event::decode(buf) + .map_err(|e| SawtoothCommunicationError::LedgerEventParse { source: e.into() })?; + // Spans of zero panic, so assign a dummy value until we thread the span correctly + let span_id = { + if event.span_id == 0 { + span::Id::from_u64(0xffffffffffffffff) + } else { + span::Id::from_u64(event.span_id) + } + }; + let model = match (event.delta, event.option_contradiction) { + (_, Some(OptionContradiction::Contradiction(contradiction))) => + Err(serde_json::from_str::(&contradiction).map_err(|e| { + SawtoothCommunicationError::LedgerEventParse { source: e.into() } + })?), + (delta, None) => { + let mut model = ProvModel::default(); + model.apply_json_ld_str(&delta).await.map_err(|e| { + SawtoothCommunicationError::LedgerEventParse { source: e.into() } + })?; + + Ok(model) + }, + }; + + let identity = { + if event.identity.is_empty() { + SignedIdentity::new_no_identity() + } else { + serde_json::from_str(&event.identity).map_err(|e| { + SawtoothCommunicationError::LedgerEventParse { source: e.into() } + })? + } + }; + Ok((Self(model, identity), Span::Span(span_id.into_u64()))) + } } #[derive(Error, Debug)] pub enum ProtocolError { - #[error("Protobuf deserialization error {source}")] - ProtobufDeserialize { - #[from] - source: prost::DecodeError, - }, - #[error("Protobuf serialization error {source}")] - ProtobufSerialize { - #[from] - source: prost::EncodeError, - }, - #[error("Serde de/serialization error {source}")] - JsonSerialize { - #[from] - source: serde_json::Error, - }, - #[error("Problem applying delta {source}")] - ProcessorError { - #[from] - source: ProcessorError, - }, - #[error("Could not compact json {source}")] - Compaction { - #[from] - source: CompactionError, - }, + #[error("Protobuf deserialization error {source}")] + ProtobufDeserialize { + #[from] + source: prost::DecodeError, + }, + #[error("Protobuf serialization error {source}")] + ProtobufSerialize { + #[from] + source: prost::EncodeError, + }, + #[error("Serde de/serialization error {source}")] + JsonSerialize { + #[from] + source: serde_json::Error, + }, + #[error("Problem applying delta {source}")] + ProcessorError { + #[from] + source: ProcessorError, + }, + #[error("Could not compact json {source}")] + Compaction { + #[from] + source: CompactionError, + }, } static PROTOCOL_VERSION: &str = "2"; @@ -108,204 +107,202 @@ static PROTOCOL_VERSION: &str = "2"; // Include the `submission` module, which is // generated from ./protos/submission.proto. pub mod messages { - #![allow(clippy::derive_partial_eq_without_eq)] + #![allow(clippy::derive_partial_eq_without_eq)] - include!(concat!(env!("OUT_DIR"), "/_.rs")); + include!(concat!(env!("OUT_DIR"), "/_.rs")); } pub async fn chronicle_committed( - span: u64, - delta: ProvModel, - identity: &SignedIdentity, + span: u64, + delta: ProvModel, + identity: &SignedIdentity, ) -> Result { - Ok(messages::Event { - version: PROTOCOL_VERSION.to_owned(), - delta: serde_json::to_string(&delta.to_json().compact_stable_order().await?)?, - span_id: span, - identity: serde_json::to_string(identity)?, - ..Default::default() - }) + Ok(messages::Event { + version: PROTOCOL_VERSION.to_owned(), + delta: serde_json::to_string(&delta.to_json().compact_stable_order().await?)?, + span_id: span, + identity: serde_json::to_string(identity)?, + ..Default::default() + }) } pub fn chronicle_contradicted( - span: u64, - contradiction: &Contradiction, - identity: &SignedIdentity, + span: u64, + contradiction: &Contradiction, + identity: &SignedIdentity, ) -> Result { - Ok(messages::Event { - version: PROTOCOL_VERSION.to_owned(), - span_id: span, - option_contradiction: Some(OptionContradiction::Contradiction(serde_json::to_string( - &contradiction, - )?)), - identity: serde_json::to_string(identity)?, - ..Default::default() - }) + Ok(messages::Event { + version: PROTOCOL_VERSION.to_owned(), + span_id: span, + option_contradiction: Some(OptionContradiction::Contradiction(serde_json::to_string( + &contradiction, + )?)), + identity: serde_json::to_string(identity)?, + ..Default::default() + }) } impl messages::Event { - pub async fn get_contradiction(&self) -> Result, ProtocolError> { - Ok(self - .option_contradiction - .as_ref() - .map(|OptionContradiction::Contradiction(s)| serde_json::from_str(s)) - .transpose()?) - } - - pub async fn get_delta(&self) -> Result { - let mut model = ProvModel::default(); - model.apply_json_ld_str(&self.delta).await?; - - Ok(model) - } + pub async fn get_contradiction(&self) -> Result, ProtocolError> { + Ok(self + .option_contradiction + .as_ref() + .map(|OptionContradiction::Contradiction(s)| serde_json::from_str(s)) + .transpose()?) + } + + pub async fn get_delta(&self) -> Result { + let mut model = ProvModel::default(); + model.apply_json_ld_str(&self.delta).await?; + + Ok(model) + } } pub fn serialize_submission(submission: &messages::Submission) -> Vec { - let mut buf = Vec::with_capacity(submission.encoded_len()); - submission.encode(&mut buf).unwrap(); - buf + let mut buf = Vec::with_capacity(submission.encoded_len()); + submission.encode(&mut buf).unwrap(); + buf } /// `Submission` protocol buffer deserializer pub fn deserialize_submission(buf: &[u8]) -> Result { - messages::Submission::decode(&mut Cursor::new(buf)) + messages::Submission::decode(&mut Cursor::new(buf)) } /// Convert a `Submission` payload from a vector of /// strings to a vector of `ChronicleOperation`s. /// Operates for version 1 of the protocol. pub async fn chronicle_operations_from_submission_v1( - submission_body: Vec, + submission_body: Vec, ) -> Result, ProcessorError> { - let mut ops = Vec::with_capacity(submission_body.len()); - for op in submission_body.iter() { - let json = serde_json::from_str(op)?; - // The inner json value should be in compacted form, - // wrapping in `ExpandedJson`, as required by `ChronicleOperation::from_json` - let op = ChronicleOperation::from_json(&json).await?; - ops.push(op); - } - Ok(ops) + let mut ops = Vec::with_capacity(submission_body.len()); + for op in submission_body.iter() { + let json = serde_json::from_str(op)?; + // The inner json value should be in compacted form, + // wrapping in `ExpandedJson`, as required by `ChronicleOperation::from_json` + let op = ChronicleOperation::from_json(&json).await?; + ops.push(op); + } + Ok(ops) } /// Convert a `Submission` payload from a vector of /// strings to a vector of `ChronicleOperation`s. /// Operates for version 2 of the protocol. pub async fn chronicle_operations_from_submission_v2( - submission_body: String, + submission_body: String, ) -> Result, ProcessorError> { - use serde_json::{json, Value}; - let json = serde_json::from_str(&submission_body)?; - - if let Value::Object(map) = json { - if let Some(version) = map.get("version") { - if version == &json!(1) { - if let Some(ops_json) = map.get("ops").and_then(|x| x.as_array()) { - let mut ops = Vec::with_capacity(ops_json.len()); - for op in ops_json { - ops.push(ChronicleOperation::from_json(op).await?); - } - Ok(ops) - } else { - Err(PayloadError::OpsNotAList.into()) - } - } else { - Err(PayloadError::VersionUnknown.into()) - } - } else { - Err(PayloadError::VersionMissing.into()) - } - } else { - Err(PayloadError::NotAnObject.into()) - } + use serde_json::{json, Value}; + let json = serde_json::from_str(&submission_body)?; + + if let Value::Object(map) = json { + if let Some(version) = map.get("version") { + if version == &json!(1) { + if let Some(ops_json) = map.get("ops").and_then(|x| x.as_array()) { + let mut ops = Vec::with_capacity(ops_json.len()); + for op in ops_json { + ops.push(ChronicleOperation::from_json(op).await?); + } + Ok(ops) + } else { + Err(PayloadError::OpsNotAList.into()) + } + } else { + Err(PayloadError::VersionUnknown.into()) + } + } else { + Err(PayloadError::VersionMissing.into()) + } + } else { + Err(PayloadError::NotAnObject.into()) + } } /// Convert a `Submission` identity from a String /// to a `SignedIdentity` pub async fn chronicle_identity_from_submission( - submission_identity: String, + submission_identity: String, ) -> Result { - Ok(serde_json::from_str(&submission_identity)?) + Ok(serde_json::from_str(&submission_identity)?) } #[cfg(test)] mod test { - use crate::protocol::{ - chronicle_operations_from_submission_v1, chronicle_operations_from_submission_v2, - ChronicleOperation, - }; - use chrono::{NaiveDateTime, TimeZone, Utc}; - use common::prov::{ - operations::{EndActivity, StartActivity}, - to_json_ld::ToJson, - ActivityId, NamespaceId, - }; - use serde_json::{json, Value}; - use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, - }; - use uuid::Uuid; - - fn construct_operations() -> Vec { - let mut hasher = DefaultHasher::new(); - "foo".hash(&mut hasher); - let n1 = hasher.finish(); - "bar".hash(&mut hasher); - let n2 = hasher.finish(); - let uuid = Uuid::from_u64_pair(n1, n2); - - let base_ms = 1234567654321; - let activity_start = - Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_millis(base_ms).unwrap()); - let activity_end = - Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_millis(base_ms + 12345).unwrap()); - - let start = ChronicleOperation::StartActivity(StartActivity { - namespace: NamespaceId::from_external_id("test-namespace", uuid), - id: ActivityId::from_external_id("test-activity"), - time: activity_start, - }); - let end = ChronicleOperation::EndActivity(EndActivity { - namespace: NamespaceId::from_external_id("test-namespace", uuid), - id: ActivityId::from_external_id("test-activity"), - time: activity_end, - }); - - vec![start, end] - } - - #[tokio::test] - async fn deserialize_submission_v1() { - let operations_expected = construct_operations(); - - let submission_body = operations_expected - .iter() - .map(|operation| serde_json::to_string(&operation.to_json().0).unwrap()) - .collect(); - - let operations_actual = chronicle_operations_from_submission_v1(submission_body) - .await - .unwrap(); - - assert_eq!(operations_expected, operations_actual); - } - - #[tokio::test] - async fn deserialize_submission_v2() { - let operations_expected = construct_operations(); - - let submission_body = - serde_json::to_string(&json!({"version": 1, "ops": operations_expected + use crate::protocol::{ + chronicle_operations_from_submission_v1, chronicle_operations_from_submission_v2, + ChronicleOperation, + }; + use chrono::{NaiveDateTime, TimeZone, Utc}; + use common::prov::{ + operations::{EndActivity, StartActivity}, + to_json_ld::ToJson, + ActivityId, NamespaceId, + }; + use serde_json::{json, Value}; + use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + }; + use uuid::Uuid; + + fn construct_operations() -> Vec { + let mut hasher = DefaultHasher::new(); + "foo".hash(&mut hasher); + let n1 = hasher.finish(); + "bar".hash(&mut hasher); + let n2 = hasher.finish(); + let uuid = Uuid::from_u64_pair(n1, n2); + + let base_ms = 1234567654321; + let activity_start = + Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_millis(base_ms).unwrap()); + let activity_end = + Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_millis(base_ms + 12345).unwrap()); + + let start = ChronicleOperation::StartActivity(StartActivity { + namespace: NamespaceId::from_external_id("test-namespace", uuid), + id: ActivityId::from_external_id("test-activity"), + time: activity_start, + }); + let end = ChronicleOperation::EndActivity(EndActivity { + namespace: NamespaceId::from_external_id("test-namespace", uuid), + id: ActivityId::from_external_id("test-activity"), + time: activity_end, + }); + + vec![start, end] + } + + #[tokio::test] + async fn deserialize_submission_v1() { + let operations_expected = construct_operations(); + + let submission_body = operations_expected + .iter() + .map(|operation| serde_json::to_string(&operation.to_json().0).unwrap()) + .collect(); + + let operations_actual = + chronicle_operations_from_submission_v1(submission_body).await.unwrap(); + + assert_eq!(operations_expected, operations_actual); + } + + #[tokio::test] + async fn deserialize_submission_v2() { + let operations_expected = construct_operations(); + + let submission_body = + serde_json::to_string(&json!({"version": 1, "ops": operations_expected .iter() .map(|operation| operation.to_json().0) .collect::>()})) - .unwrap(); + .unwrap(); - let operations_actual = chronicle_operations_from_submission_v2(submission_body) - .await - .unwrap(); + let operations_actual = + chronicle_operations_from_submission_v2(submission_body).await.unwrap(); - assert_eq!(operations_expected, operations_actual); - } + assert_eq!(operations_expected, operations_actual); + } } diff --git a/crates/chronicle-protocol/src/settings.rs b/crates/chronicle-protocol/src/settings.rs index dbe0c80ac..ad4071eef 100644 --- a/crates/chronicle-protocol/src/settings.rs +++ b/crates/chronicle-protocol/src/settings.rs @@ -1,7 +1,7 @@ use std::iter::repeat; use async_stl_client::{ - error::SawtoothCommunicationError, ledger::LedgerReader, messages::Setting, + error::SawtoothCommunicationError, ledger::LedgerReader, messages::Setting, }; use k256::sha2::{Digest, Sha256}; use prost::Message; @@ -10,22 +10,22 @@ use tracing::error; use crate::ChronicleLedger; fn setting_key_to_address(key: &str) -> String { - let mut address = String::new(); - address.push_str("000000"); - address.push_str( - &key.splitn(4, '.') - .chain(repeat("")) - .map(short_hash) - .take(4) - .collect::>() - .join(""), - ); + let mut address = String::new(); + address.push_str("000000"); + address.push_str( + &key.splitn(4, '.') + .chain(repeat("")) + .map(short_hash) + .take(4) + .collect::>() + .join(""), + ); - address + address } fn short_hash(s: &str) -> String { - hex::encode(Sha256::digest(s.as_bytes()))[..16].to_string() + hex::encode(Sha256::digest(s.as_bytes()))[..16].to_string() } /// Generates a Sawtooth address for a given setting key. @@ -53,7 +53,7 @@ fn short_hash(s: &str) -> String { /// assert_eq!(address, "000000a87cb5eafdcca6a8b79606fb3afea5bdab274474a6aa82c1c0cbf0fbcaf64c0b"); /// ``` pub fn sawtooth_settings_address(s: &str) -> String { - setting_key_to_address(s) + setting_key_to_address(s) } /// This `SettingsReader` struct is used for extracting particular configuration @@ -61,56 +61,54 @@ pub fn sawtooth_settings_address(s: &str) -> String { pub struct SettingsReader(ChronicleLedger); impl SettingsReader { - pub fn new(reader: ChronicleLedger) -> Self { - Self(reader) - } + pub fn new(reader: ChronicleLedger) -> Self { + Self(reader) + } - /// Async function that returns the value of a specific configuration setting, given its key. - /// - /// # Arguments - /// * `key` - a reference to a string that contains the key for the setting to retrieve. - /// - /// # Errors - /// If the value is not found, returns a `SawtoothCommunicationError`, which indicates that there was an error in communicating with the Sawtooth network. - /// - /// Settings values are not uniform, so we return a `Vec` for further processing - pub async fn read_settings(&self, key: &str) -> Result { - let address = sawtooth_settings_address(key); - loop { - let res = self.0.get_state_entry(&address).await; + /// Async function that returns the value of a specific configuration setting, given its key. + /// + /// # Arguments + /// * `key` - a reference to a string that contains the key for the setting to retrieve. + /// + /// # Errors + /// If the value is not found, returns a `SawtoothCommunicationError`, which indicates that + /// there was an error in communicating with the Sawtooth network. + /// + /// Settings values are not uniform, so we return a `Vec` for further processing + pub async fn read_settings(&self, key: &str) -> Result { + let address = sawtooth_settings_address(key); + loop { + let res = self.0.get_state_entry(&address).await; - if let Err(e) = res { - error!("Error reading settings: {}", e); - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - continue; - } + if let Err(e) = res { + error!("Error reading settings: {}", e); + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + continue + } - return Ok(Setting::decode(&*res.unwrap())?); - } - } + return Ok(Setting::decode(&*res.unwrap())?) + } + } } #[derive(Debug, Clone)] pub struct OpaSettings { - pub policy_name: String, - pub entrypoint: String, + pub policy_name: String, + pub entrypoint: String, } pub async fn read_opa_settings( - settings: &SettingsReader, + settings: &SettingsReader, ) -> Result { - let policy_id = settings.read_settings("chronicle.opa.policy_name").await?; - let entrypoint = settings.read_settings("chronicle.opa.entrypoint").await?; - let policy_id = policy_id - .entries - .first() - .ok_or_else(|| SawtoothCommunicationError::MalformedMessage)?; - let entrypoint = entrypoint - .entries - .first() - .ok_or_else(|| SawtoothCommunicationError::MalformedMessage)?; - Ok(OpaSettings { - policy_name: policy_id.value.clone(), - entrypoint: entrypoint.value.clone(), - }) + let policy_id = settings.read_settings("chronicle.opa.policy_name").await?; + let entrypoint = settings.read_settings("chronicle.opa.entrypoint").await?; + let policy_id = policy_id + .entries + .first() + .ok_or_else(|| SawtoothCommunicationError::MalformedMessage)?; + let entrypoint = entrypoint + .entries + .first() + .ok_or_else(|| SawtoothCommunicationError::MalformedMessage)?; + Ok(OpaSettings { policy_name: policy_id.value.clone(), entrypoint: entrypoint.value.clone() }) } diff --git a/crates/chronicle-signing/src/embedded_secret_manager_source.rs b/crates/chronicle-signing/src/embedded_secret_manager_source.rs index 63716674b..6d4d964ae 100644 --- a/crates/chronicle-signing/src/embedded_secret_manager_source.rs +++ b/crates/chronicle-signing/src/embedded_secret_manager_source.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use k256::{ - pkcs8::{EncodePrivateKey, LineEnding}, - SecretKey, + pkcs8::{EncodePrivateKey, LineEnding}, + SecretKey, }; use rand::{rngs::StdRng, SeedableRng}; use secret_vault::{Secret, SecretMetadata, SecretVaultRef, SecretVaultResult, SecretsSource}; @@ -13,66 +13,60 @@ use tracing::debug; use crate::SecretError; pub struct EmbeddedSecretManagerSource { - secrets: Arc>>>, - deterministic: bool, + secrets: Arc>>>, + deterministic: bool, } impl EmbeddedSecretManagerSource { - pub fn new() -> Self { - Self { - secrets: Arc::new(Mutex::new(HashMap::new())), - deterministic: false, - } - } + pub fn new() -> Self { + Self { secrets: Arc::new(Mutex::new(HashMap::new())), deterministic: false } + } - pub fn new_deterministic() -> Self { - Self { - secrets: Arc::new(Mutex::new(HashMap::new())), - deterministic: true, - } - } + pub fn new_deterministic() -> Self { + Self { secrets: Arc::new(Mutex::new(HashMap::new())), deterministic: true } + } } fn new_signing_key(deterministic: bool, index: usize) -> Result, SecretError> { - let secret = if deterministic { - SecretKey::random(StdRng::seed_from_u64(index as _)) - } else { - SecretKey::random(StdRng::from_entropy()) - }; - let privpem = secret - .to_pkcs8_pem(LineEnding::CRLF) - .map_err(|_| SecretError::InvalidPrivateKey)?; + let secret = if deterministic { + SecretKey::random(StdRng::seed_from_u64(index as _)) + } else { + SecretKey::random(StdRng::from_entropy()) + }; + let privpem = secret + .to_pkcs8_pem(LineEnding::CRLF) + .map_err(|_| SecretError::InvalidPrivateKey)?; - Ok(privpem.as_bytes().into()) + Ok(privpem.as_bytes().into()) } #[async_trait] impl SecretsSource for EmbeddedSecretManagerSource { - fn name(&self) -> String { - "EmbeddedSecretManager".to_string() - } + fn name(&self) -> String { + "EmbeddedSecretManager".to_string() + } - // Simply create and cache a new signing key for each novel reference - async fn get_secrets( - &self, - references: &[SecretVaultRef], - ) -> SecretVaultResult> { - debug!(get_secrets=?references, "Getting secrets from embedded source"); + // Simply create and cache a new signing key for each novel reference + async fn get_secrets( + &self, + references: &[SecretVaultRef], + ) -> SecretVaultResult> { + debug!(get_secrets=?references, "Getting secrets from embedded source"); - let mut result_map: HashMap = HashMap::new(); - let mut secrets = self.secrets.lock().await; - for (index, secret_ref) in references.iter().enumerate() { - let secret = secrets.entry(secret_ref.clone()).or_insert_with(|| { - let secret = new_signing_key(self.deterministic, index).unwrap(); - secret.to_vec() - }); + let mut result_map: HashMap = HashMap::new(); + let mut secrets = self.secrets.lock().await; + for (index, secret_ref) in references.iter().enumerate() { + let secret = secrets.entry(secret_ref.clone()).or_insert_with(|| { + let secret = new_signing_key(self.deterministic, index).unwrap(); + secret.to_vec() + }); - let secret_value = SecretValue::from(secret); - let metadata = SecretMetadata::create_from_ref(secret_ref); + let secret_value = SecretValue::from(secret); + let metadata = SecretMetadata::create_from_ref(secret_ref); - result_map.insert(secret_ref.clone(), Secret::new(secret_value, metadata)); - } + result_map.insert(secret_ref.clone(), Secret::new(secret_value, metadata)); + } - Ok(result_map) - } + Ok(result_map) + } } diff --git a/crates/chronicle-signing/src/error.rs b/crates/chronicle-signing/src/error.rs new file mode 100644 index 000000000..018af22c0 --- /dev/null +++ b/crates/chronicle-signing/src/error.rs @@ -0,0 +1,19 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum SecretError { + #[error("Invalid public key")] + InvalidPublicKey, + #[error("Invalid private key")] + InvalidPrivateKey, + #[error("No public key found")] + NoPublicKeyFound, + #[error("No private key found")] + NoPrivateKeyFound, + + #[error("Vault {source}")] + SecretVault { + #[from] + source: anyhow::Error, + }, +} diff --git a/crates/chronicle-signing/src/lib.rs b/crates/chronicle-signing/src/lib.rs index 710b6ad69..f19657a76 100644 --- a/crates/chronicle-signing/src/lib.rs +++ b/crates/chronicle-signing/src/lib.rs @@ -1,17 +1,17 @@ use k256::{ - ecdsa::{ - signature::{Signer, Verifier}, - Signature, SigningKey, VerifyingKey, - }, - pkcs8::DecodePrivateKey, + ecdsa::{ + signature::{Signer, Verifier}, + Signature, SigningKey, VerifyingKey, + }, + pkcs8::DecodePrivateKey, }; use secret_vault::{ - errors::SecretVaultError, FilesSource, FilesSourceOptions, MultipleSecretsSources, SecretName, - SecretNamespace, SecretVaultBuilder, SecretVaultRef, SecretVaultView, + errors::SecretVaultError, FilesSource, FilesSourceOptions, MultipleSecretsSources, SecretName, + SecretNamespace, SecretVaultBuilder, SecretVaultRef, SecretVaultView, }; use std::{ - path::{Path, PathBuf}, - sync::Arc, + path::{Path, PathBuf}, + sync::Arc, }; use thiserror::Error; use tracing::instrument; @@ -28,566 +28,494 @@ pub static OPA_PK: &str = "opa-pk"; #[derive(Error, Debug)] pub enum SecretError { - #[error("Invalid public key")] - InvalidPublicKey, - #[error("Invalid private key")] - InvalidPrivateKey, - #[error("No public key found")] - NoPublicKeyFound, - #[error("No private key found")] - NoPrivateKeyFound, - - #[error("Vault {source}")] - SecretVault { - #[from] - source: SecretVaultError, - }, + #[error("Invalid public key")] + InvalidPublicKey, + #[error("Invalid private key")] + InvalidPrivateKey, + #[error("No public key found")] + NoPublicKeyFound, + #[error("No private key found")] + NoPrivateKeyFound, + + #[error("Vault {source}")] + SecretVault { + #[from] + source: SecretVaultError, + }, } pub enum ChronicleSecretsOptions { - // Connect to hashicorp vault for secrets - Vault(vault_secret_manager_source::VaultSecretManagerSourceOptions), - // Generate secrets in memory on demand - Embedded, - //Deterministically generate secrets for testing - Test, - //Filesystem based keys - Filesystem(PathBuf), + // Connect to hashicorp vault for secrets + Vault(vault_secret_manager_source::VaultSecretManagerSourceOptions), + // Generate secrets in memory on demand + Embedded, + //Deterministically generate secrets for testing + Test, + //Filesystem based keys + Filesystem(PathBuf), } impl ChronicleSecretsOptions { - // Get secrets from Hashicorp vault - pub fn stored_in_vault( - vault_url: &Url, - token: &str, - mount_path: &str, - ) -> ChronicleSecretsOptions { - ChronicleSecretsOptions::Vault( - vault_secret_manager_source::VaultSecretManagerSourceOptions::new( - vault_url.clone(), - token, - mount_path, - ), - ) - } - - // Load secrets from filesystem at path - pub fn stored_at_path(path: &Path) -> ChronicleSecretsOptions { - ChronicleSecretsOptions::Filesystem(path.to_owned()) - } - - // Generate secrets in memory on demand - pub fn generate_in_memory() -> ChronicleSecretsOptions { - ChronicleSecretsOptions::Embedded - } - - // Generate deterministic secrets in memory on demand - pub fn test_keys() -> ChronicleSecretsOptions { - ChronicleSecretsOptions::Test - } + // Get secrets from Hashicorp vault + pub fn stored_in_vault( + vault_url: &Url, + token: &str, + mount_path: &str, + ) -> ChronicleSecretsOptions { + ChronicleSecretsOptions::Vault( + vault_secret_manager_source::VaultSecretManagerSourceOptions::new( + vault_url.clone(), + token, + mount_path, + ), + ) + } + + // Load secrets from filesystem at path + pub fn stored_at_path(path: &Path) -> ChronicleSecretsOptions { + ChronicleSecretsOptions::Filesystem(path.to_owned()) + } + + // Generate secrets in memory on demand + pub fn generate_in_memory() -> ChronicleSecretsOptions { + ChronicleSecretsOptions::Embedded + } + + // Generate deterministic secrets in memory on demand + pub fn test_keys() -> ChronicleSecretsOptions { + ChronicleSecretsOptions::Test + } } #[derive(Clone)] pub struct ChronicleSigning { - vault: Arc>>, + vault: Arc>>, } -impl std::fmt::Debug for ChronicleSigning { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ChronicleSecrets").finish() - } +impl core::fmt::Debug for ChronicleSigning { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ChronicleSecrets").finish() + } } impl ChronicleSigning { - pub async fn new( - // Secrets are namespace / name pairs - required_secret_names: Vec<(String, String)>, - // Secret stores are namespaced - options: Vec<(String, ChronicleSecretsOptions)>, - ) -> Result { - let mut multi_source = MultipleSecretsSources::new(); - let required_secret_refs: Vec<_> = required_secret_names - .into_iter() - .map(|(namespace, name)| { - SecretVaultRef::new(SecretName::new(name)) - .with_namespace(SecretNamespace::new(namespace)) - }) - .collect(); - - for options in options { - match options { - (namespace, ChronicleSecretsOptions::Embedded) => { - let source = embedded_secret_manager_source::EmbeddedSecretManagerSource::new(); - multi_source = - multi_source.add_source(&SecretNamespace::new(namespace), source); - } - (namespace, ChronicleSecretsOptions::Test) => { - let source = embedded_secret_manager_source::EmbeddedSecretManagerSource::new_deterministic(); - multi_source = - multi_source.add_source(&SecretNamespace::new(namespace), source); - } - (namespace, ChronicleSecretsOptions::Vault(options)) => { - let source = - vault_secret_manager_source::VaultSecretManagerSource::with_options( - options, - ) - .await?; - multi_source = - multi_source.add_source(&SecretNamespace::new(namespace), source); - } - (namespace, ChronicleSecretsOptions::Filesystem(path)) => { - let source = FilesSource::with_options(FilesSourceOptions { - root_path: Some(path.into_boxed_path()), - }); - multi_source = - multi_source.add_source(&SecretNamespace::new(namespace), source); - } - } - } - - let vault = SecretVaultBuilder::with_source(multi_source) - .with_secret_refs(required_secret_refs.iter().collect()) - .build()?; - - vault.refresh().await?; - Ok(Self { - vault: Arc::new(tokio::sync::Mutex::new(Box::new(vault.viewer()))), - }) - } + pub async fn new( + // Secrets are namespace / name pairs + required_secret_names: Vec<(String, String)>, + // Secret stores are namespaced + options: Vec<(String, ChronicleSecretsOptions)>, + ) -> Result { + let mut multi_source = MultipleSecretsSources::new(); + let required_secret_refs: Vec<_> = required_secret_names + .into_iter() + .map(|(namespace, name)| { + SecretVaultRef::new(SecretName::new(name)) + .with_namespace(SecretNamespace::new(namespace)) + }) + .collect(); + + for options in options { + match options { + (namespace, ChronicleSecretsOptions::Embedded) => { + let source = embedded_secret_manager_source::EmbeddedSecretManagerSource::new(); + multi_source = + multi_source.add_source(&SecretNamespace::new(namespace), source); + }, + (namespace, ChronicleSecretsOptions::Test) => { + let source = embedded_secret_manager_source::EmbeddedSecretManagerSource::new_deterministic(); + multi_source = + multi_source.add_source(&SecretNamespace::new(namespace), source); + }, + (namespace, ChronicleSecretsOptions::Vault(options)) => { + let source = + vault_secret_manager_source::VaultSecretManagerSource::with_options( + options, + ) + .await?; + multi_source = + multi_source.add_source(&SecretNamespace::new(namespace), source); + }, + (namespace, ChronicleSecretsOptions::Filesystem(path)) => { + let source = FilesSource::with_options(FilesSourceOptions { + root_path: Some(path.into_boxed_path()), + }); + multi_source = + multi_source.add_source(&SecretNamespace::new(namespace), source); + }, + } + } + + let vault = SecretVaultBuilder::with_source(multi_source) + .with_secret_refs(required_secret_refs.iter().collect()) + .build()?; + + vault.refresh().await?; + Ok(Self { vault: Arc::new(tokio::sync::Mutex::new(Box::new(vault.viewer()))) }) + } } #[async_trait::async_trait] pub trait WithSecret { - async fn with_signing_key( - &self, - secret_namespace: &str, - secret_name: &str, - f: F, - ) -> Result - where - F: Fn(SigningKey) -> T, - F: Send, - T: Send; - async fn with_verifying_key( - &self, - secret_namespace: &str, - secret_name: &str, - f: F, - ) -> Result - where - F: Fn(VerifyingKey) -> T, - F: Send, - T: Send; - - async fn verifying_key( - &self, - secret_namespace: &str, - secret_name: &str, - ) -> Result; + async fn with_signing_key( + &self, + secret_namespace: &str, + secret_name: &str, + f: F, + ) -> Result + where + F: Fn(SigningKey) -> T, + F: Send, + T: Send; + async fn with_verifying_key( + &self, + secret_namespace: &str, + secret_name: &str, + f: F, + ) -> Result + where + F: Fn(VerifyingKey) -> T, + F: Send, + T: Send; + + async fn verifying_key( + &self, + secret_namespace: &str, + secret_name: &str, + ) -> Result; } #[async_trait::async_trait] impl WithSecret for ChronicleSigning { - async fn with_signing_key( - &self, - secret_namespace: &str, - secret_name: &str, - f: F, - ) -> Result - where - F: Fn(SigningKey) -> T, - F: Send, - T: Send, - { - let secret_ref = SecretVaultRef::new(SecretName::new(secret_name.to_owned())) - .with_namespace(secret_namespace.into()); - let secret = self - .vault - .lock() - .await - .require_secret_by_ref(&secret_ref) - .await?; - - let signing_result = secret.value.exposed_in_as_str(|secret| { - ( - // Not semantically the same thing as we re-use f - SigningKey::from_pkcs8_pem(&secret) - .map_err(|_| SecretError::InvalidPrivateKey) - .map(&f), - secret, - ) - }); - - Ok(signing_result?) - } - - async fn with_verifying_key( - &self, - secret_namespace: &str, - secret_name: &str, - f: F, - ) -> Result - where - F: Fn(VerifyingKey) -> T, - F: Send, - T: Send, - { - let secret_ref = SecretVaultRef::new(SecretName::new(secret_name.to_owned())) - .with_namespace(secret_namespace.into()); - let secret = self - .vault - .lock() - .await - .require_secret_by_ref(&secret_ref) - .await?; - - let signing_result = secret.value.exposed_in_as_str(|secret| { - ( - SigningKey::from_pkcs8_pem(&secret) - .map_err(|_| SecretError::InvalidPrivateKey) - .map(|signing_key| f(signing_key.verifying_key())), - secret, - ) - }); - - Ok(signing_result?) - } - - async fn verifying_key( - &self, - secret_namespace: &str, - secret_name: &str, - ) -> Result { - let secret_ref = SecretVaultRef::new(SecretName::new(secret_name.to_owned())) - .with_namespace(secret_namespace.into()); - let secret = self - .vault - .lock() - .await - .require_secret_by_ref(&secret_ref) - .await?; - - let key = secret.value.exposed_in_as_str(|secret| { - ( - SigningKey::from_pkcs8_pem(&secret) - .map_err(|_| SecretError::InvalidPrivateKey) - .map(|signing_key| signing_key.verifying_key()), - secret, - ) - }); - - Ok(key?) - } + async fn with_signing_key( + &self, + secret_namespace: &str, + secret_name: &str, + f: F, + ) -> Result + where + F: Fn(SigningKey) -> T, + F: Send, + T: Send, + { + let secret_ref = SecretVaultRef::new(SecretName::new(secret_name.to_owned())) + .with_namespace(secret_namespace.into()); + let secret = self.vault.lock().await.require_secret_by_ref(&secret_ref).await?; + + let signing_result = secret.value.exposed_in_as_str(|secret| { + ( + // Not semantically the same thing as we re-use f + SigningKey::from_pkcs8_pem(&secret) + .map_err(|_| SecretError::InvalidPrivateKey) + .map(&f), + secret, + ) + }); + + Ok(signing_result?) + } + + async fn with_verifying_key( + &self, + secret_namespace: &str, + secret_name: &str, + f: F, + ) -> Result + where + F: Fn(VerifyingKey) -> T, + F: Send, + T: Send, + { + let secret_ref = SecretVaultRef::new(SecretName::new(secret_name.to_owned())) + .with_namespace(secret_namespace.into()); + let secret = self.vault.lock().await.require_secret_by_ref(&secret_ref).await?; + + let signing_result = secret.value.exposed_in_as_str(|secret| { + ( + SigningKey::from_pkcs8_pem(&secret) + .map_err(|_| SecretError::InvalidPrivateKey) + .map(|signing_key| f(signing_key.verifying_key())), + secret, + ) + }); + + Ok(signing_result?) + } + + async fn verifying_key( + &self, + secret_namespace: &str, + secret_name: &str, + ) -> Result { + let secret_ref = SecretVaultRef::new(SecretName::new(secret_name.to_owned())) + .with_namespace(secret_namespace.into()); + let secret = self.vault.lock().await.require_secret_by_ref(&secret_ref).await?; + + let key = secret.value.exposed_in_as_str(|secret| { + ( + SigningKey::from_pkcs8_pem(&secret) + .map_err(|_| SecretError::InvalidPrivateKey) + .map(|signing_key| signing_key.verifying_key()), + secret, + ) + }); + + Ok(key?) + } } /// Trait for signing with a key known by chronicle #[async_trait::async_trait] pub trait ChronicleSigner { - /// Sign data with the a known key and return a signature - async fn sign( - &self, - secret_namespace: &str, - secret_name: &str, - data: &[u8], - ) -> Result; - - /// Verify a signature with a known key - async fn verify( - &self, - secret_namespace: &str, - secret_name: &str, - data: &[u8], - signature: &[u8], - ) -> Result; + /// Sign data with the a known key and return a signature + async fn sign( + &self, + secret_namespace: &str, + secret_name: &str, + data: &[u8], + ) -> Result; + + /// Verify a signature with a known key + async fn verify( + &self, + secret_namespace: &str, + secret_name: &str, + data: &[u8], + signature: &[u8], + ) -> Result; } #[async_trait::async_trait] impl ChronicleSigner for T { - /// Sign data with the chronicle key and return a signature - async fn sign( - &self, - secret_namespace: &str, - secret_name: &str, - data: &[u8], - ) -> Result { - self.with_signing_key(secret_namespace, secret_name, |signing_key| { - let s: Signature = signing_key.sign(data); - s - }) - .await - } - - /// Verify a signature with the chronicle key - async fn verify( - &self, - secret_namespace: &str, - secret_name: &str, - data: &[u8], - signature: &[u8], - ) -> Result { - self.with_verifying_key(secret_namespace, secret_name, |verifying_key| { - let signature: Signature = k256::ecdsa::signature::Signature::from_bytes(signature) - .map_err(|_| SecretError::InvalidPublicKey)?; - - Ok(verifying_key.verify(data, &signature).is_ok()) - }) - .await? - } + /// Sign data with the chronicle key and return a signature + async fn sign( + &self, + secret_namespace: &str, + secret_name: &str, + data: &[u8], + ) -> Result { + self.with_signing_key(secret_namespace, secret_name, |signing_key| { + let s: Signature = signing_key.sign(data); + s + }) + .await + } + + /// Verify a signature with the chronicle key + async fn verify( + &self, + secret_namespace: &str, + secret_name: &str, + data: &[u8], + signature: &[u8], + ) -> Result { + self.with_verifying_key(secret_namespace, secret_name, |verifying_key| { + let signature: Signature = k256::ecdsa::signature::Signature::from_bytes(signature) + .map_err(|_| SecretError::InvalidPublicKey)?; + + Ok(verifying_key.verify(data, &signature).is_ok()) + }) + .await? + } } /// Trait for signing with a key known by the batcher #[async_trait::async_trait] pub trait BatcherKnownKeyNamesSigner { - /// Sign data with the batcher key and return a signature in low-s form, as this - /// is required by sawtooth for batcher signatures - async fn batcher_sign(&self, data: &[u8]) -> Result, SecretError>; + /// Sign data with the batcher key and return a signature in low-s form, as this + /// is required by sawtooth for batcher signatures + async fn batcher_sign(&self, data: &[u8]) -> Result, SecretError>; - /// Verify a signature with the batcher key - async fn batcher_verify(&self, data: &[u8], signature: &[u8]) -> Result; + /// Verify a signature with the batcher key + async fn batcher_verify(&self, data: &[u8], signature: &[u8]) -> Result; - /// Get the verifying key for the batcher key - async fn batcher_verifying(&self) -> Result; + /// Get the verifying key for the batcher key + async fn batcher_verifying(&self) -> Result; } /// Trait for signing with a key known by chronicle #[async_trait::async_trait] pub trait ChronicleKnownKeyNamesSigner { - /// Sign data with the chronicle key and return a signature - async fn chronicle_sign(&self, data: &[u8]) -> Result, SecretError>; + /// Sign data with the chronicle key and return a signature + async fn chronicle_sign(&self, data: &[u8]) -> Result, SecretError>; - /// Verify a signature with the chronicle key - async fn chronicle_verify(&self, data: &[u8], signature: &[u8]) -> Result; + /// Verify a signature with the chronicle key + async fn chronicle_verify(&self, data: &[u8], signature: &[u8]) -> Result; - /// Get the verifying key for the chronicle key - async fn chronicle_verifying(&self) -> Result; + /// Get the verifying key for the chronicle key + async fn chronicle_verifying(&self) -> Result; } /// Trait for signing with a key known by OPA #[async_trait::async_trait] pub trait OpaKnownKeyNamesSigner { - /// Sign data with the OPA key and return a signature - async fn opa_sign(&self, data: &[u8]) -> Result, SecretError>; + /// Sign data with the OPA key and return a signature + async fn opa_sign(&self, data: &[u8]) -> Result, SecretError>; - /// Verify a signature with the OPA key - async fn opa_verify(&self, data: &[u8], signature: &[u8]) -> Result; + /// Verify a signature with the OPA key + async fn opa_verify(&self, data: &[u8], signature: &[u8]) -> Result; - /// Get the verifying key for the OPA key - async fn opa_verifying(&self) -> Result; + /// Get the verifying key for the OPA key + async fn opa_verifying(&self) -> Result; } #[async_trait::async_trait] impl BatcherKnownKeyNamesSigner for T { - // Sign with the batcher key and return a signature in low-s form, as this - // is required by sawtooth for batcher signatures - #[instrument(skip(self,data), level = "trace", name = "batcher_sign", fields(namespace = BATCHER_NAMESPACE, pk = BATCHER_PK))] - async fn batcher_sign(&self, data: &[u8]) -> Result, SecretError> { - let s = self.sign(BATCHER_NAMESPACE, BATCHER_PK, data).await?; - - let s = s.normalize_s().unwrap_or(s); - - Ok(s.to_vec()) - } - - #[instrument(skip(self,data,signature), level = "trace", name = "batcher_verify", fields(namespace = BATCHER_NAMESPACE, pk = BATCHER_PK))] - async fn batcher_verify(&self, data: &[u8], signature: &[u8]) -> Result { - self.verify(BATCHER_NAMESPACE, BATCHER_PK, data, signature) - .await - } - - #[instrument(skip(self), level = "trace", name = "batcher_verifying", fields(namespace = BATCHER_NAMESPACE, pk = BATCHER_PK))] - async fn batcher_verifying(&self) -> Result { - self.verifying_key(BATCHER_NAMESPACE, BATCHER_PK).await - } + // Sign with the batcher key and return a signature in low-s form, as this + // is required by sawtooth for batcher signatures + #[instrument(skip(self,data), level = "trace", name = "batcher_sign", fields(namespace = BATCHER_NAMESPACE, pk = BATCHER_PK))] + async fn batcher_sign(&self, data: &[u8]) -> Result, SecretError> { + let s = self.sign(BATCHER_NAMESPACE, BATCHER_PK, data).await?; + + let s = s.normalize_s().unwrap_or(s); + + Ok(s.to_vec()) + } + + #[instrument(skip(self,data,signature), level = "trace", name = "batcher_verify", fields(namespace = BATCHER_NAMESPACE, pk = BATCHER_PK))] + async fn batcher_verify(&self, data: &[u8], signature: &[u8]) -> Result { + self.verify(BATCHER_NAMESPACE, BATCHER_PK, data, signature).await + } + + #[instrument(skip(self), level = "trace", name = "batcher_verifying", fields(namespace = BATCHER_NAMESPACE, pk = BATCHER_PK))] + async fn batcher_verifying(&self) -> Result { + self.verifying_key(BATCHER_NAMESPACE, BATCHER_PK).await + } } #[async_trait::async_trait] impl ChronicleKnownKeyNamesSigner for T { - #[instrument(skip(self,data), level = "trace", name = "chronicle_sign", fields(namespace = CHRONICLE_NAMESPACE, pk = CHRONICLE_PK))] - async fn chronicle_sign(&self, data: &[u8]) -> Result, SecretError> { - Ok(self - .sign(CHRONICLE_NAMESPACE, CHRONICLE_PK, data) - .await? - .to_vec()) - } - - #[instrument(skip(self,data,signature), level = "trace", name = "chronicle_verify", fields(namespace = CHRONICLE_NAMESPACE, pk = CHRONICLE_PK))] - async fn chronicle_verify(&self, data: &[u8], signature: &[u8]) -> Result { - self.verify(CHRONICLE_NAMESPACE, CHRONICLE_PK, data, signature) - .await - } - - #[instrument(skip(self), level = "trace", name = "chronicle_verifying", fields(namespace = CHRONICLE_NAMESPACE, pk = CHRONICLE_PK))] - async fn chronicle_verifying(&self) -> Result { - self.verifying_key(CHRONICLE_NAMESPACE, CHRONICLE_PK).await - } + #[instrument(skip(self,data), level = "trace", name = "chronicle_sign", fields(namespace = CHRONICLE_NAMESPACE, pk = CHRONICLE_PK))] + async fn chronicle_sign(&self, data: &[u8]) -> Result, SecretError> { + Ok(self.sign(CHRONICLE_NAMESPACE, CHRONICLE_PK, data).await?.to_vec()) + } + + #[instrument(skip(self,data,signature), level = "trace", name = "chronicle_verify", fields(namespace = CHRONICLE_NAMESPACE, pk = CHRONICLE_PK))] + async fn chronicle_verify(&self, data: &[u8], signature: &[u8]) -> Result { + self.verify(CHRONICLE_NAMESPACE, CHRONICLE_PK, data, signature).await + } + + #[instrument(skip(self), level = "trace", name = "chronicle_verifying", fields(namespace = CHRONICLE_NAMESPACE, pk = CHRONICLE_PK))] + async fn chronicle_verifying(&self) -> Result { + self.verifying_key(CHRONICLE_NAMESPACE, CHRONICLE_PK).await + } } #[async_trait::async_trait] impl OpaKnownKeyNamesSigner for T { - #[instrument(skip(self), level = "trace", name = "opa_sign", fields(namespace = OPA_NAMESPACE, pk = OPA_PK))] - async fn opa_sign(&self, data: &[u8]) -> Result, SecretError> { - let s = self.sign(OPA_NAMESPACE, OPA_PK, data).await?; + #[instrument(skip(self), level = "trace", name = "opa_sign", fields(namespace = OPA_NAMESPACE, pk = OPA_PK))] + async fn opa_sign(&self, data: &[u8]) -> Result, SecretError> { + let s = self.sign(OPA_NAMESPACE, OPA_PK, data).await?; - let s = s.normalize_s().unwrap_or(s); + let s = s.normalize_s().unwrap_or(s); - Ok(s.to_vec()) - } + Ok(s.to_vec()) + } - #[instrument(skip(self,data,signature), level = "trace", name = "opa_verify", fields(namespace = OPA_NAMESPACE, pk = OPA_PK))] - async fn opa_verify(&self, data: &[u8], signature: &[u8]) -> Result { - self.verify(OPA_NAMESPACE, OPA_PK, data, signature).await - } + #[instrument(skip(self,data,signature), level = "trace", name = "opa_verify", fields(namespace = OPA_NAMESPACE, pk = OPA_PK))] + async fn opa_verify(&self, data: &[u8], signature: &[u8]) -> Result { + self.verify(OPA_NAMESPACE, OPA_PK, data, signature).await + } - #[instrument(skip(self), level = "trace", name = "opa_verifying", fields(namespace = OPA_NAMESPACE, pk = OPA_PK))] - async fn opa_verifying(&self) -> Result { - self.verifying_key(OPA_NAMESPACE, OPA_PK).await - } + #[instrument(skip(self), level = "trace", name = "opa_verifying", fields(namespace = OPA_NAMESPACE, pk = OPA_PK))] + async fn opa_verifying(&self) -> Result { + self.verifying_key(OPA_NAMESPACE, OPA_PK).await + } } pub fn chronicle_secret_names() -> Vec<(String, String)> { - vec![ - (CHRONICLE_NAMESPACE.to_string(), CHRONICLE_PK.to_string()), - (BATCHER_NAMESPACE.to_string(), BATCHER_PK.to_string()), - ] + vec![ + (CHRONICLE_NAMESPACE.to_string(), CHRONICLE_PK.to_string()), + (BATCHER_NAMESPACE.to_string(), BATCHER_PK.to_string()), + ] } pub fn opa_secret_names() -> Vec<(String, String)> { - vec![ - (OPA_NAMESPACE.to_string(), OPA_PK.to_string()), - (BATCHER_NAMESPACE.to_string(), BATCHER_PK.to_string()), - ] + vec![ + (OPA_NAMESPACE.to_string(), OPA_PK.to_string()), + (BATCHER_NAMESPACE.to_string(), BATCHER_PK.to_string()), + ] } #[cfg(test)] mod tests { - use super::*; - use k256::schnorr::signature::Signature; - - #[tokio::test] - async fn embedded_keys() { - let secrets = ChronicleSigning::new( - chronicle_secret_names(), - vec![( - CHRONICLE_NAMESPACE.to_string(), - ChronicleSecretsOptions::Embedded, - )], - ) - .await - .unwrap(); - - secrets - .with_signing_key(CHRONICLE_NAMESPACE, "chronicle-pk", |signing_key| { - assert_eq!( - signing_key.to_bytes().len(), - 32, - "Signing key should be 32 bytes" - ); - }) - .await - .unwrap(); - - secrets - .with_verifying_key(CHRONICLE_NAMESPACE, "chronicle-pk", |verifying_key| { - assert_eq!( - verifying_key.to_bytes().len(), - 33, - "Verifying key should be 33 bytes" - ); - }) - .await - .unwrap(); - - let sig = secrets - .sign( - CHRONICLE_NAMESPACE, - "chronicle-pk", - "hello world".as_bytes(), - ) - .await - .unwrap(); - - assert!(secrets - .verify( - CHRONICLE_NAMESPACE, - "chronicle-pk", - "hello world".as_bytes(), - sig.as_bytes() - ) - .await - .unwrap()); - - assert!(!secrets - .verify( - CHRONICLE_NAMESPACE, - "chronicle-pk", - "boom".as_bytes(), - sig.as_bytes() - ) - .await - .unwrap()); - } - - #[tokio::test] - async fn vault_keys() { - let secrets = ChronicleSigning::new( - chronicle_secret_names(), - vec![( - CHRONICLE_NAMESPACE.to_string(), - ChronicleSecretsOptions::Embedded, - )], - ) - .await - .unwrap(); - - secrets - .with_signing_key(CHRONICLE_NAMESPACE, "chronicle-pk", |signing_key| { - assert_eq!( - signing_key.to_bytes().len(), - 32, - "Signing key should be 32 bytes" - ); - }) - .await - .unwrap(); - - secrets - .with_verifying_key(CHRONICLE_NAMESPACE, "chronicle-pk", |verifying_key| { - assert_eq!( - verifying_key.to_bytes().len(), - 33, - "Verifying key should be 33 bytes" - ); - }) - .await - .unwrap(); - - let sig = secrets - .sign( - CHRONICLE_NAMESPACE, - "chronicle-pk", - "hello world".as_bytes(), - ) - .await - .unwrap(); - - assert!(secrets - .verify( - CHRONICLE_NAMESPACE, - "chronicle-pk", - "hello world".as_bytes(), - sig.as_bytes() - ) - .await - .unwrap()); - - assert!(!secrets - .verify( - CHRONICLE_NAMESPACE, - "chronicle-pk", - "boom".as_bytes(), - sig.as_bytes() - ) - .await - .unwrap()); - } + use super::*; + use k256::schnorr::signature::Signature; + + #[tokio::test] + async fn embedded_keys() { + let secrets = ChronicleSigning::new( + chronicle_secret_names(), + vec![(CHRONICLE_NAMESPACE.to_string(), ChronicleSecretsOptions::Embedded)], + ) + .await + .unwrap(); + + secrets + .with_signing_key(CHRONICLE_NAMESPACE, "chronicle-pk", |signing_key| { + assert_eq!(signing_key.to_bytes().len(), 32, "Signing key should be 32 bytes"); + }) + .await + .unwrap(); + + secrets + .with_verifying_key(CHRONICLE_NAMESPACE, "chronicle-pk", |verifying_key| { + assert_eq!(verifying_key.to_bytes().len(), 33, "Verifying key should be 33 bytes"); + }) + .await + .unwrap(); + + let sig = secrets + .sign(CHRONICLE_NAMESPACE, "chronicle-pk", "hello world".as_bytes()) + .await + .unwrap(); + + assert!(secrets + .verify(CHRONICLE_NAMESPACE, "chronicle-pk", "hello world".as_bytes(), sig.as_bytes()) + .await + .unwrap()); + + assert!(!secrets + .verify(CHRONICLE_NAMESPACE, "chronicle-pk", "boom".as_bytes(), sig.as_bytes()) + .await + .unwrap()); + } + + #[tokio::test] + async fn vault_keys() { + let secrets = ChronicleSigning::new( + chronicle_secret_names(), + vec![(CHRONICLE_NAMESPACE.to_string(), ChronicleSecretsOptions::Embedded)], + ) + .await + .unwrap(); + + secrets + .with_signing_key(CHRONICLE_NAMESPACE, "chronicle-pk", |signing_key| { + assert_eq!(signing_key.to_bytes().len(), 32, "Signing key should be 32 bytes"); + }) + .await + .unwrap(); + + secrets + .with_verifying_key(CHRONICLE_NAMESPACE, "chronicle-pk", |verifying_key| { + assert_eq!(verifying_key.to_bytes().len(), 33, "Verifying key should be 33 bytes"); + }) + .await + .unwrap(); + + let sig = secrets + .sign(CHRONICLE_NAMESPACE, "chronicle-pk", "hello world".as_bytes()) + .await + .unwrap(); + + assert!(secrets + .verify(CHRONICLE_NAMESPACE, "chronicle-pk", "hello world".as_bytes(), sig.as_bytes()) + .await + .unwrap()); + + assert!(!secrets + .verify(CHRONICLE_NAMESPACE, "chronicle-pk", "boom".as_bytes(), sig.as_bytes()) + .await + .unwrap()); + } } diff --git a/crates/chronicle-signing/src/types.rs b/crates/chronicle-signing/src/types.rs new file mode 100644 index 000000000..1c72fbe2b --- /dev/null +++ b/crates/chronicle-signing/src/types.rs @@ -0,0 +1,203 @@ +#[async_trait::async_trait] +pub trait WithSecret { + async fn with_signing_key( + &self, + secret_namespace: &str, + secret_name: &str, + f: F, + ) -> Result + where + F: Fn(SigningKey) -> T, + F: Send, + T: Send; + async fn with_verifying_key( + &self, + secret_namespace: &str, + secret_name: &str, + f: F, + ) -> Result + where + F: Fn(VerifyingKey) -> T, + F: Send, + T: Send; + + async fn verifying_key( + &self, + secret_namespace: &str, + secret_name: &str, + ) -> Result; +} + +#[async_trait::async_trait] +impl WithSecret for ChronicleSigning { + async fn with_signing_key( + &self, + secret_namespace: &str, + secret_name: &str, + f: F, + ) -> Result + where + F: Fn(SigningKey) -> T, + F: Send, + T: Send, + { + let secret_ref = SecretVaultRef::new(SecretName::new(secret_name.to_owned())) + .with_namespace(secret_namespace.into()); + let secret = self.vault.lock().await.require_secret_by_ref(&secret_ref).await?; + + let signing_result = secret.value.exposed_in_as_str(|secret| { + ( + // Not semantically the same thing as we re-use f + SigningKey::from_pkcs8_pem(&secret) + .map_err(|_| SecretError::InvalidPrivateKey) + .map(&f), + secret, + ) + }); + + Ok(signing_result?) + } + + async fn with_verifying_key( + &self, + secret_namespace: &str, + secret_name: &str, + f: F, + ) -> Result + where + F: Fn(VerifyingKey) -> T, + F: Send, + T: Send, + { + let secret_ref = SecretVaultRef::new(SecretName::new(secret_name.to_owned())) + .with_namespace(secret_namespace.into()); + let secret = self.vault.lock().await.require_secret_by_ref(&secret_ref).await?; + + let signing_result = secret.value.exposed_in_as_str(|secret| { + ( + SigningKey::from_pkcs8_pem(&secret) + .map_err(|_| SecretError::InvalidPrivateKey) + .map(|signing_key| f(signing_key.verifying_key())), + secret, + ) + }); + + Ok(signing_result?) + } + + async fn verifying_key( + &self, + secret_namespace: &str, + secret_name: &str, + ) -> Result { + let secret_ref = SecretVaultRef::new(SecretName::new(secret_name.to_owned())) + .with_namespace(secret_namespace.into()); + let secret = self.vault.lock().await.require_secret_by_ref(&secret_ref).await?; + + let key = secret.value.exposed_in_as_str(|secret| { + ( + SigningKey::from_pkcs8_pem(&secret) + .map_err(|_| SecretError::InvalidPrivateKey) + .map(|signing_key| signing_key.verifying_key()), + secret, + ) + }); + + Ok(key?) + } +} + +/// Trait for signing with a key known by chronicle +#[async_trait::async_trait] +pub trait ChronicleSigner { + /// Sign data with the a known key and return a signature + async fn sign( + &self, + secret_namespace: &str, + secret_name: &str, + data: &[u8], + ) -> Result; + + /// Verify a signature with a known key + async fn verify( + &self, + secret_namespace: &str, + secret_name: &str, + data: &[u8], + signature: &[u8], + ) -> Result; +} + +#[async_trait::async_trait] +impl ChronicleSigner for T { + /// Sign data with the chronicle key and return a signature + async fn sign( + &self, + secret_namespace: &str, + secret_name: &str, + data: &[u8], + ) -> Result { + self.with_signing_key(secret_namespace, secret_name, |signing_key| { + let s: Signature = signing_key.sign(data); + s + }) + .await + } + + /// Verify a signature with the chronicle key + async fn verify( + &self, + secret_namespace: &str, + secret_name: &str, + data: &[u8], + signature: &[u8], + ) -> Result { + self.with_verifying_key(secret_namespace, secret_name, |verifying_key| { + let signature: Signature = k256::ecdsa::signature::Signature::from_bytes(signature) + .map_err(|_| SecretError::InvalidPublicKey)?; + + Ok(verifying_key.verify(data, &signature).is_ok()) + }) + .await? + } +} + +/// Trait for signing with a key known by the batcher +#[async_trait::async_trait] +pub trait BatcherKnownKeyNamesSigner { + /// Sign data with the batcher key and return a signature in low-s form, as this + /// is required by sawtooth for batcher signatures + async fn batcher_sign(&self, data: &[u8]) -> Result, SecretError>; + + /// Verify a signature with the batcher key + async fn batcher_verify(&self, data: &[u8], signature: &[u8]) -> Result; + + /// Get the verifying key for the batcher key + async fn batcher_verifying(&self) -> Result; +} + +/// Trait for signing with a key known by chronicle +#[async_trait::async_trait] +pub trait ChronicleKnownKeyNamesSigner { + /// Sign data with the chronicle key and return a signature + async fn chronicle_sign(&self, data: &[u8]) -> Result, SecretError>; + + /// Verify a signature with the chronicle key + async fn chronicle_verify(&self, data: &[u8], signature: &[u8]) -> Result; + + /// Get the verifying key for the chronicle key + async fn chronicle_verifying(&self) -> Result; +} + +/// Trait for signing with a key known by OPA +#[async_trait::async_trait] +pub trait OpaKnownKeyNamesSigner { + /// Sign data with the OPA key and return a signature + async fn opa_sign(&self, data: &[u8]) -> Result, SecretError>; + + /// Verify a signature with the OPA key + async fn opa_verify(&self, data: &[u8], signature: &[u8]) -> Result; + + /// Get the verifying key for the OPA key + async fn opa_verifying(&self) -> Result; +} diff --git a/crates/chronicle-signing/src/vault_secret_manager_source.rs b/crates/chronicle-signing/src/vault_secret_manager_source.rs index 35ffa4ba3..e4a08ca2f 100644 --- a/crates/chronicle-signing/src/vault_secret_manager_source.rs +++ b/crates/chronicle-signing/src/vault_secret_manager_source.rs @@ -2,126 +2,120 @@ use std::{collections::HashMap, sync::Arc}; use async_trait::*; use secret_vault::{ - errors::{SecretVaultError, SecretVaultErrorPublicGenericDetails, SecretsSourceError}, - Secret, SecretMetadata, SecretVaultRef, SecretVaultResult, SecretsSource, + errors::{SecretVaultError, SecretVaultErrorPublicGenericDetails, SecretsSourceError}, + Secret, SecretMetadata, SecretVaultRef, SecretVaultResult, SecretsSource, }; use secret_vault_value::SecretValue; use tokio::sync::Mutex; use tracing::*; use url::Url; use vaultrs::{ - client::{VaultClient, VaultClientSettingsBuilder}, - kv2, + client::{VaultClient, VaultClientSettingsBuilder}, + kv2, }; #[derive(Debug, Clone, Eq, PartialEq)] pub struct VaultSecretManagerSourceOptions { - pub vault_url: Url, - pub token: String, - pub mount_path: String, + pub vault_url: Url, + pub token: String, + pub mount_path: String, } impl VaultSecretManagerSourceOptions { - pub fn new(vault_url: Url, token: &str, mount_path: &str) -> Self { - VaultSecretManagerSourceOptions { - vault_url, - token: token.to_owned(), - mount_path: mount_path.to_owned(), - } - } + pub fn new(vault_url: Url, token: &str, mount_path: &str) -> Self { + VaultSecretManagerSourceOptions { + vault_url, + token: token.to_owned(), + mount_path: mount_path.to_owned(), + } + } } #[derive(Clone)] pub struct VaultSecretManagerSource { - options: VaultSecretManagerSourceOptions, - client: Arc>, + options: VaultSecretManagerSourceOptions, + client: Arc>, } impl VaultSecretManagerSource { - pub async fn with_options(options: VaultSecretManagerSourceOptions) -> SecretVaultResult { - Ok(VaultSecretManagerSource { - options: options.clone(), - client: Arc::new(Mutex::new( - VaultClient::new( - VaultClientSettingsBuilder::default() - .address(options.vault_url.as_str()) - .token(options.token) - .build() - .unwrap(), - ) - .map_err(|e| { - SecretVaultError::SecretsSourceError( - SecretsSourceError::new( - SecretVaultErrorPublicGenericDetails::new(format!("{}", e)), - format!("Vault error: {}", e), - ) - .with_root_cause(Box::new(e)), - ) - })?, - )), - }) - } + pub async fn with_options(options: VaultSecretManagerSourceOptions) -> SecretVaultResult { + Ok(VaultSecretManagerSource { + options: options.clone(), + client: Arc::new(Mutex::new( + VaultClient::new( + VaultClientSettingsBuilder::default() + .address(options.vault_url.as_str()) + .token(options.token) + .build() + .unwrap(), + ) + .map_err(|e| { + SecretVaultError::SecretsSourceError( + SecretsSourceError::new( + SecretVaultErrorPublicGenericDetails::new(format!("{}", e)), + format!("Vault error: {}", e), + ) + .with_root_cause(Box::new(e)), + ) + })?, + )), + }) + } } #[async_trait] impl SecretsSource for VaultSecretManagerSource { - fn name(&self) -> String { - "HashiVaultSecretManager".to_string() - } + fn name(&self) -> String { + "HashiVaultSecretManager".to_string() + } - async fn get_secrets( - &self, - references: &[SecretVaultRef], - ) -> SecretVaultResult> { - let mut result_map: HashMap = HashMap::new(); - let client = &*self.client.lock().await; + async fn get_secrets( + &self, + references: &[SecretVaultRef], + ) -> SecretVaultResult> { + let mut result_map: HashMap = HashMap::new(); + let client = &*self.client.lock().await; - let mut results = vec![]; - for secret_ref in references { - results.push(( - secret_ref.clone(), - kv2::read( - client, - &self.options.mount_path, - secret_ref.key.secret_name.as_ref(), - ) - .await, - )); - } + let mut results = vec![]; + for secret_ref in references { + results.push(( + secret_ref.clone(), + kv2::read(client, &self.options.mount_path, secret_ref.key.secret_name.as_ref()) + .await, + )); + } - for (secret_ref, result) in results { - match result { - Ok(vault_secret) => { - let metadata = SecretMetadata::create_from_ref(&secret_ref); - result_map.insert( - secret_ref.clone(), - Secret::new(SecretValue::new(vault_secret), metadata), - ); - } - Err(err) => { - error!( - "Unable to read secret or secret version {}:{}/{:?}: {}.", - self.options.mount_path, - &secret_ref.key.secret_name, - &secret_ref.key.secret_version, - err - ); - return Err(SecretVaultError::SecretsSourceError( - SecretsSourceError::new( - SecretVaultErrorPublicGenericDetails::new(format!( - "Unable to read secret or secret version {}/{:?}: {}.", - self.options.mount_path, &secret_ref.key.secret_name, err - )), - format!( - "Unable to read secret or secret version {}/{:?}: {}.", - self.options.mount_path, &secret_ref.key.secret_name, err - ), - ), - )); - } - } - } + for (secret_ref, result) in results { + match result { + Ok(vault_secret) => { + let metadata = SecretMetadata::create_from_ref(&secret_ref); + result_map.insert( + secret_ref.clone(), + Secret::new(SecretValue::new(vault_secret), metadata), + ); + }, + Err(err) => { + error!( + "Unable to read secret or secret version {}:{}/{:?}: {}.", + self.options.mount_path, + &secret_ref.key.secret_name, + &secret_ref.key.secret_version, + err + ); + return Err(SecretVaultError::SecretsSourceError(SecretsSourceError::new( + SecretVaultErrorPublicGenericDetails::new(format!( + "Unable to read secret or secret version {}/{:?}: {}.", + self.options.mount_path, &secret_ref.key.secret_name, err + )), + format!( + "Unable to read secret or secret version {}/{:?}: {}.", + self.options.mount_path, &secret_ref.key.secret_name, err + ), + ))) + }, + } + } - Ok(result_map) - } + Ok(result_map) + } } diff --git a/crates/chronicle-synth/build.rs b/crates/chronicle-synth/build.rs index 5a1d86bbe..afb2c9546 100644 --- a/crates/chronicle-synth/build.rs +++ b/crates/chronicle-synth/build.rs @@ -1,8 +1,8 @@ fn main() { - //Create a .VERSION file containing 'local' if it does not exist + //Create a .VERSION file containing 'local' if it does not exist - let version_file = std::path::Path::new("../../.VERSION"); - if !version_file.exists() { - std::fs::write(version_file, "local").expect("Unable to write file"); - } + let version_file = std::path::Path::new("../../.VERSION"); + if !version_file.exists() { + std::fs::write(version_file, "local").expect("Unable to write file"); + } } diff --git a/crates/chronicle-synth/src/collection.rs b/crates/chronicle-synth/src/collection.rs index ae8d41421..79bf899a7 100644 --- a/crates/chronicle-synth/src/collection.rs +++ b/crates/chronicle-synth/src/collection.rs @@ -1,218 +1,219 @@ use std::{ - fmt::Display, - fs::File, - path::{Path, PathBuf}, + fmt::Display, + fs::File, + path::{Path, PathBuf}, }; use serde_json::Value; use crate::error::ChronicleSynthError; -/// Represents a Synth collection that generates a Chronicle operation or component-generator of an operation collection. +/// Represents a Synth collection that generates a Chronicle operation or component-generator of an +/// operation collection. #[derive(Debug)] pub enum Collection { - Operation(Operation), - Generator(Generator), + Operation(Operation), + Generator(Generator), } /// `Operation` refers to a Synth schema collection that generates a Chronicle operation. /// An `Operation` usually has dependencies in the form of component [`Generator`]s. #[derive(Debug)] pub enum Operation { - ActivityExists, - ActivityUses, - AgentActsOnBehalfOf, - AgentExists, - CreateNamespace, - EndActivity, - EntityDerive, - EntityExists, - SetActivityAttributes, - SetAgentAttributes, - SetEntityAttributes, - StartActivity, - WasAssociatedWith, - WasAssociatedWithHasRole, - WasAttributedTo, - WasGeneratedBy, - WasInformedBy, - DomainCollection(DomainCollection), + ActivityExists, + ActivityUses, + AgentActsOnBehalfOf, + AgentExists, + CreateNamespace, + EndActivity, + EntityDerive, + EntityExists, + SetActivityAttributes, + SetAgentAttributes, + SetEntityAttributes, + StartActivity, + WasAssociatedWith, + WasAssociatedWithHasRole, + WasAttributedTo, + WasGeneratedBy, + WasInformedBy, + DomainCollection(DomainCollection), } /// `Generator` refers to a Synth schema collection that generates a component of a Chronicle /// operation, as opposed to being an operation itself. A `Generator` should have no dependencies. #[derive(Debug)] pub enum Generator { - ActivityName, - SecondActivityName, - AgentName, - SecondAgentName, - Attribute, - Attributes, - DateTime, - DomainTypeId, - EntityName, - SecondEntityName, - Namespace, - NamespaceUuid, - Role, - Roles, - SameNamespaceName, - SameNamespaceUuid, - DomainCollection(DomainCollection), + ActivityName, + SecondActivityName, + AgentName, + SecondAgentName, + Attribute, + Attributes, + DateTime, + DomainTypeId, + EntityName, + SecondEntityName, + Namespace, + NamespaceUuid, + Role, + Roles, + SameNamespaceName, + SameNamespaceUuid, + DomainCollection(DomainCollection), } /// Represents a Synth collection that is generated specifically for a Chronicle domain. #[derive(Debug)] pub struct DomainCollection { - pub name: String, - pub schema: Value, + pub name: String, + pub schema: Value, } impl DomainCollection { - pub fn new(name: impl Into, schema: Value) -> Self { - let name = name.into(); - Self { name, schema } - } + pub fn new(name: impl Into, schema: Value) -> Self { + let name = name.into(); + Self { name, schema } + } } pub trait CollectionHandling { - fn name(&self) -> String - where - Self: Display, - { - self.to_string() - } - - fn path(&self) -> PathBuf - where - Self: Display, - { - Path::new(&format!("{}.json", self)).to_path_buf() - } - - fn json_schema(&self) -> Result - where - Self: Display; + fn name(&self) -> String + where + Self: Display, + { + self.to_string() + } + + fn path(&self) -> PathBuf + where + Self: Display, + { + Path::new(&format!("{}.json", self)).to_path_buf() + } + + fn json_schema(&self) -> Result + where + Self: Display; } impl From for Collection { - fn from(operation: Operation) -> Self { - Self::Operation(operation) - } + fn from(operation: Operation) -> Self { + Self::Operation(operation) + } } impl From for Collection { - fn from(generator: Generator) -> Self { - Self::Generator(generator) - } + fn from(generator: Generator) -> Self { + Self::Generator(generator) + } } impl Display for Collection { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Collection::Operation(operation) => write!(f, "{}", operation), - Collection::Generator(generator) => write!(f, "{}", generator), - } - } + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Collection::Operation(operation) => write!(f, "{}", operation), + Collection::Generator(generator) => write!(f, "{}", generator), + } + } } impl CollectionHandling for Collection { - fn json_schema(&self) -> Result { - match self { - Collection::Operation(operation) => operation.json_schema(), - Collection::Generator(generator) => generator.json_schema(), - } - } + fn json_schema(&self) -> Result { + match self { + Collection::Operation(operation) => operation.json_schema(), + Collection::Generator(generator) => generator.json_schema(), + } + } } impl CollectionHandling for Operation { - fn json_schema(&self) -> Result - where - Self: Display, - { - match self { - Self::DomainCollection(domain_collection) => Ok(domain_collection.schema.to_owned()), - _ => { - let path = self.path(); - let reader = File::open(path)?; - let schema: serde_json::Value = serde_json::from_reader(reader)?; - Ok(schema) - } - } - } + fn json_schema(&self) -> Result + where + Self: Display, + { + match self { + Self::DomainCollection(domain_collection) => Ok(domain_collection.schema.to_owned()), + _ => { + let path = self.path(); + let reader = File::open(path)?; + let schema: serde_json::Value = serde_json::from_reader(reader)?; + Ok(schema) + }, + } + } } impl Display for Operation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::ActivityExists => "activity_exists", - Self::ActivityUses => "activity_uses", - Self::AgentActsOnBehalfOf => "agent_acts_on_behalf_of", - Self::AgentExists => "agent_exists", - Self::CreateNamespace => "create_namespace", - Self::EndActivity => "end_activity", - Self::EntityDerive => "entity_derive", - Self::EntityExists => "entity_exists", - Self::SetActivityAttributes => "set_activity_attributes", - Self::SetAgentAttributes => "set_agent_attributes", - Self::SetEntityAttributes => "set_entity_attributes", - Self::StartActivity => "start_activity", - Self::WasAssociatedWith => "was_associated_with", - Self::WasAssociatedWithHasRole => "was_associated_with_has_role", - Self::WasAttributedTo => "was_attributed_to", - Self::WasGeneratedBy => "was_generated_by", - Self::WasInformedBy => "was_informed_by", - Self::DomainCollection(domain_collection) => &domain_collection.name, - } - ) - } + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{}", + match self { + Self::ActivityExists => "activity_exists", + Self::ActivityUses => "activity_uses", + Self::AgentActsOnBehalfOf => "agent_acts_on_behalf_of", + Self::AgentExists => "agent_exists", + Self::CreateNamespace => "create_namespace", + Self::EndActivity => "end_activity", + Self::EntityDerive => "entity_derive", + Self::EntityExists => "entity_exists", + Self::SetActivityAttributes => "set_activity_attributes", + Self::SetAgentAttributes => "set_agent_attributes", + Self::SetEntityAttributes => "set_entity_attributes", + Self::StartActivity => "start_activity", + Self::WasAssociatedWith => "was_associated_with", + Self::WasAssociatedWithHasRole => "was_associated_with_has_role", + Self::WasAttributedTo => "was_attributed_to", + Self::WasGeneratedBy => "was_generated_by", + Self::WasInformedBy => "was_informed_by", + Self::DomainCollection(domain_collection) => &domain_collection.name, + } + ) + } } impl CollectionHandling for Generator { - fn json_schema(&self) -> Result - where - Self: Display, - { - match self { - Self::DomainCollection(domain_collection) => Ok(domain_collection.schema.to_owned()), - _ => { - let path = self.path(); - let reader = File::open(path)?; - let schema: serde_json::Value = serde_json::from_reader(reader)?; - Ok(schema) - } - } - } + fn json_schema(&self) -> Result + where + Self: Display, + { + match self { + Self::DomainCollection(domain_collection) => Ok(domain_collection.schema.to_owned()), + _ => { + let path = self.path(); + let reader = File::open(path)?; + let schema: serde_json::Value = serde_json::from_reader(reader)?; + Ok(schema) + }, + } + } } impl Display for Generator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::ActivityName => "activity_name", - Self::SecondActivityName => "second_activity_name", - Self::AgentName => "agent_name", - Self::SecondAgentName => "second_agent_name", - Self::Attribute => "attribute", - Self::Attributes => "attributes", - Self::DateTime => "date_time", - Self::DomainTypeId => "domain_type_id", - Self::EntityName => "entity_name", - Self::SecondEntityName => "second_entity_name", - Self::Namespace => "namespace", - Self::NamespaceUuid => "namespace_uuid", - Self::Role => "role", - Self::Roles => "roles", - Self::SameNamespaceName => "same_namespace_name", - Self::SameNamespaceUuid => "same_namespace_uuid", - Self::DomainCollection(dc) => &dc.name, - } - ) - } + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{}", + match self { + Self::ActivityName => "activity_name", + Self::SecondActivityName => "second_activity_name", + Self::AgentName => "agent_name", + Self::SecondAgentName => "second_agent_name", + Self::Attribute => "attribute", + Self::Attributes => "attributes", + Self::DateTime => "date_time", + Self::DomainTypeId => "domain_type_id", + Self::EntityName => "entity_name", + Self::SecondEntityName => "second_entity_name", + Self::Namespace => "namespace", + Self::NamespaceUuid => "namespace_uuid", + Self::Role => "role", + Self::Roles => "roles", + Self::SameNamespaceName => "same_namespace_name", + Self::SameNamespaceUuid => "same_namespace_uuid", + Self::DomainCollection(dc) => &dc.name, + } + ) + } } diff --git a/crates/chronicle-synth/src/domain.rs b/crates/chronicle-synth/src/domain.rs index 572aa1f37..93f802891 100644 --- a/crates/chronicle-synth/src/domain.rs +++ b/crates/chronicle-synth/src/domain.rs @@ -5,123 +5,109 @@ use serde::Deserialize; use serde_json::json; use crate::{ - collection::{Collection, DomainCollection, Generator, Operation}, - error::ChronicleSynthError, + collection::{Collection, DomainCollection, Generator, Operation}, + error::ChronicleSynthError, }; #[derive(Debug)] pub struct TypesAttributesRoles { - pub name: String, - entities: BTreeMap>, - agents: BTreeMap>, - activities: BTreeMap>, - roles: Vec, + pub name: String, + entities: BTreeMap>, + agents: BTreeMap>, + activities: BTreeMap>, + roles: Vec, } impl TypesAttributesRoles { - /// Creates a new `TypesAttributesRoles` instance from a YAML file at the specified path. - /// - /// # Arguments - /// - /// * `path` - The path to the YAML file. - /// - /// # Returns - /// - /// A `Result` containing the `TypesAttributesRoles` instance, or an error if the operation fails. - pub fn from_file(path: &Path) -> Result { - #[derive(Debug, Deserialize)] - struct ChronicleDomain { - #[serde(skip)] - _roles_doc: Option, - roles: Vec, - name: String, - attributes: BTreeMap, - entities: BTreeMap, - agents: BTreeMap, - activities: BTreeMap, - } - - impl ChronicleDomain { - fn from_path(path: &Path) -> Result { - let yaml: String = std::fs::read_to_string(path)?; - let domain: ChronicleDomain = serde_yaml::from_str(&yaml)?; - Ok(domain) - } - } - - impl From for TypesAttributesRoles { - fn from(value: ChronicleDomain) -> Self { - let mut attribute_types = BTreeMap::new(); - attribute_types.extend(value.attributes); - - let entities = value - .entities - .into_iter() - .map(|(entity_type, attributes)| { - ( - entity_type, - attributes.link_attribute_types(&attribute_types), - ) - }) - .collect(); - let agents = value - .agents - .into_iter() - .map(|(agent_type, attributes)| { - ( - agent_type, - attributes.link_attribute_types(&attribute_types), - ) - }) - .collect(); - let activities = value - .activities - .into_iter() - .map(|(activity_type, attributes)| { - ( - activity_type, - attributes.link_attribute_types(&attribute_types), - ) - }) - .collect(); - - Self { - name: value.name, - entities, - agents, - activities, - roles: value.roles, - } - } - } - - let domain = ChronicleDomain::from_path(path)?; - Ok(domain.into()) - } - - pub fn generate_domain_collections(&self) -> Result, ChronicleSynthError> { - let mut collections = vec![self.generate_roles()?]; - collections.extend(self.generate_activity_schema()?); - collections.extend(self.generate_agent_schema()?); - collections.extend(self.generate_entity_schema()?); - Ok(collections) - } - - fn generate_roles(&self) -> Result { - generate_roles(&self.roles) - } - - fn generate_activity_schema(&self) -> Result, ChronicleSynthError> { - generate_schema(&self.activities) - } - - fn generate_agent_schema(&self) -> Result, ChronicleSynthError> { - generate_schema(&self.agents) - } - - fn generate_entity_schema(&self) -> Result, ChronicleSynthError> { - generate_schema(&self.entities) - } + /// Creates a new `TypesAttributesRoles` instance from a YAML file at the specified path. + /// + /// # Arguments + /// + /// * `path` - The path to the YAML file. + /// + /// # Returns + /// + /// A `Result` containing the `TypesAttributesRoles` instance, or an error if the operation + /// fails. + pub fn from_file(path: &Path) -> Result { + #[derive(Debug, Deserialize)] + struct ChronicleDomain { + #[serde(skip)] + _roles_doc: Option, + roles: Vec, + name: String, + attributes: BTreeMap, + entities: BTreeMap, + agents: BTreeMap, + activities: BTreeMap, + } + + impl ChronicleDomain { + fn from_path(path: &Path) -> Result { + let yaml: String = std::fs::read_to_string(path)?; + let domain: ChronicleDomain = serde_yaml::from_str(&yaml)?; + Ok(domain) + } + } + + impl From for TypesAttributesRoles { + fn from(value: ChronicleDomain) -> Self { + let mut attribute_types = BTreeMap::new(); + attribute_types.extend(value.attributes); + + let entities = value + .entities + .into_iter() + .map(|(entity_type, attributes)| { + (entity_type, attributes.link_attribute_types(&attribute_types)) + }) + .collect(); + let agents = value + .agents + .into_iter() + .map(|(agent_type, attributes)| { + (agent_type, attributes.link_attribute_types(&attribute_types)) + }) + .collect(); + let activities = value + .activities + .into_iter() + .map(|(activity_type, attributes)| { + (activity_type, attributes.link_attribute_types(&attribute_types)) + }) + .collect(); + + Self { name: value.name, entities, agents, activities, roles: value.roles } + } + } + + let domain = ChronicleDomain::from_path(path)?; + Ok(domain.into()) + } + + pub fn generate_domain_collections(&self) -> Result, ChronicleSynthError> { + let mut collections = vec![self.generate_roles()?]; + collections.extend(self.generate_activity_schema()?); + collections.extend(self.generate_agent_schema()?); + collections.extend(self.generate_entity_schema()?); + Ok(collections) + } + + fn generate_roles(&self) -> Result { + generate_roles(&self.roles) + } + + fn generate_activity_schema(&self) -> Result, ChronicleSynthError> { + generate_schema(&self.activities) + } + + fn generate_agent_schema(&self) -> Result, ChronicleSynthError> { + generate_schema(&self.agents) + } + + fn generate_entity_schema(&self) -> Result, ChronicleSynthError> { + generate_schema(&self.entities) + } } #[derive(Debug, Deserialize, Eq, PartialEq, Hash, PartialOrd, Ord)] @@ -131,237 +117,234 @@ struct AttributeType(String); struct ParsedDomainType(String); impl ParsedDomainType { - fn as_str(&self) -> &str { - &self.0 - } + fn as_str(&self) -> &str { + &self.0 + } } #[derive(Debug, Deserialize)] struct Role(String); impl Role { - fn as_str(&self) -> &str { - &self.0 - } + fn as_str(&self) -> &str { + &self.0 + } } #[derive(Debug)] enum SynthType { - String, - Object, - Number, - Bool, + String, + Object, + Number, + Bool, } impl From<&ChroniclePrimitive> for SynthType { - fn from(value: &ChroniclePrimitive) -> Self { - match value.r#type { - PrimitiveType::String => SynthType::String, - PrimitiveType::JSON => SynthType::Object, - PrimitiveType::Int => SynthType::Number, - PrimitiveType::Bool => SynthType::Bool, - } - } + fn from(value: &ChroniclePrimitive) -> Self { + match value.r#type { + PrimitiveType::String => SynthType::String, + PrimitiveType::JSON => SynthType::Object, + PrimitiveType::Int => SynthType::Number, + PrimitiveType::Bool => SynthType::Bool, + } + } } #[derive(Debug, Deserialize)] struct ChroniclePrimitive { - #[serde(skip)] - _doc: Option, - #[serde(rename = "type")] - r#type: PrimitiveType, + #[serde(skip)] + _doc: Option, + #[serde(rename = "type")] + r#type: PrimitiveType, } #[derive(Debug, Deserialize)] struct Attributes { - #[serde(skip)] - _doc: Option, - attributes: Vec, + #[serde(skip)] + _doc: Option, + attributes: Vec, } impl Attributes { - fn link_attribute_types( - self, - attribute_types: &BTreeMap, - ) -> BTreeMap { - let mut attr = BTreeMap::new(); - for attr_type in self.attributes { - let r#type: SynthType = attribute_types.get(&attr_type).unwrap().into(); - attr.insert(attr_type, r#type); - } - attr - } + fn link_attribute_types( + self, + attribute_types: &BTreeMap, + ) -> BTreeMap { + let mut attr = BTreeMap::new(); + for attr_type in self.attributes { + let r#type: SynthType = attribute_types.get(&attr_type).unwrap().into(); + attr.insert(attr_type, r#type); + } + attr + } } fn generate_roles(roles: &[Role]) -> Result { - let mut variants = vec![json!({ - "type": "string", - "constant": "UNSPECIFIED" - })]; - - // Uppercase guaranteed by the Linter - for role in roles { - variants.push(json!({ - "type": "string", - "constant": role.as_str() - })); - } - - let roles = json!({ - "type": "one_of", - "variants": variants - }); - - let domain_collection = DomainCollection::new("roles", roles); - - Ok(Collection::Generator(Generator::DomainCollection( - domain_collection, - ))) + let mut variants = vec![json!({ + "type": "string", + "constant": "UNSPECIFIED" + })]; + + // Uppercase guaranteed by the Linter + for role in roles { + variants.push(json!({ + "type": "string", + "constant": role.as_str() + })); + } + + let roles = json!({ + "type": "one_of", + "variants": variants + }); + + let domain_collection = DomainCollection::new("roles", roles); + + Ok(Collection::Generator(Generator::DomainCollection(domain_collection))) } fn domain_type_id_for_domain(ParsedDomainType(r#type): &ParsedDomainType) -> Collection { - let domain_type_id = json!({ - "type": "string", - "constant": r#type - }); + let domain_type_id = json!({ + "type": "string", + "constant": r#type + }); - let collection_name = format!("{}_domain_type_id", r#type.to_lowercase()); - let domain_collection = DomainCollection::new(collection_name, domain_type_id); + let collection_name = format!("{}_domain_type_id", r#type.to_lowercase()); + let domain_collection = DomainCollection::new(collection_name, domain_type_id); - Collection::Generator(Generator::DomainCollection(domain_collection)) + Collection::Generator(Generator::DomainCollection(domain_collection)) } fn set_attributes(type_name_lower: &str) -> Collection { - let type_collection = format!("@{}_attributes", type_name_lower); - let type_domain_type = format!("@{}_domain_type_id", type_name_lower); - let type_attributes = json!({ - "type": "object", - "@id": "_:n1", - "@type": { - "type": "array", - "length": 1, - "content": "http://btp.works/chronicleoperations/ns#SetAttributes" - }, - "http://btp.works/chronicleoperations/ns#activityName": "@activity_name", - "http://btp.works/chronicleoperations/ns#attributes": { - "type": "array", - "length": 1, - "content": { - "type": "object", - "@type": { - "type": "string", - "constant": "@json" - }, - "@value": type_collection - } - }, - "http://btp.works/chronicleoperations/ns#domaintypeId": { - "type": "array", - "length": 1, - "content": { - "type": "object", - "@value": type_domain_type - } - }, - "http://btp.works/chronicleoperations/ns#namespaceName": "@same_namespace_name", - "http://btp.works/chronicleoperations/ns#namespaceUuid": "@same_namespace_uuid" - }); - - let name = format!("set_{}_attributes", type_name_lower); - let domain_collection = DomainCollection::new(name, type_attributes); - Collection::Operation(Operation::DomainCollection(domain_collection)) + let type_collection = format!("@{}_attributes", type_name_lower); + let type_domain_type = format!("@{}_domain_type_id", type_name_lower); + let type_attributes = json!({ + "type": "object", + "@id": "_:n1", + "@type": { + "type": "array", + "length": 1, + "content": "http://btp.works/chronicleoperations/ns#SetAttributes" + }, + "http://btp.works/chronicleoperations/ns#activityName": "@activity_name", + "http://btp.works/chronicleoperations/ns#attributes": { + "type": "array", + "length": 1, + "content": { + "type": "object", + "@type": { + "type": "string", + "constant": "@json" + }, + "@value": type_collection + } + }, + "http://btp.works/chronicleoperations/ns#domaintypeId": { + "type": "array", + "length": 1, + "content": { + "type": "object", + "@value": type_domain_type + } + }, + "http://btp.works/chronicleoperations/ns#namespaceName": "@same_namespace_name", + "http://btp.works/chronicleoperations/ns#namespaceUuid": "@same_namespace_uuid" + }); + + let name = format!("set_{}_attributes", type_name_lower); + let domain_collection = DomainCollection::new(name, type_attributes); + Collection::Operation(Operation::DomainCollection(domain_collection)) } fn type_attribute_variants( - type_name_lower: &str, - attributes: &BTreeMap, + type_name_lower: &str, + attributes: &BTreeMap, ) -> Result { - let mut type_attribute_variants: BTreeMap = maplit::btreemap! { - "type".to_string() => json!("object"), - }; - - for (AttributeType(attribute), r#type) in attributes { - let type_attribute_variant = match r#type { - SynthType::String => { - json!({ - "type": "string", - "faker": { - "generator": "bs_noun" - } - }) - } - SynthType::Number => { - json!({ - "type": "number", - "subtype": "u32" - }) - } - SynthType::Bool => { - json!({ - "type": "bool", - "frequency": 0.5 - }) - } - // Object will be an empty object. - // This is something that could be tweaked on a case by case basis given some domain knowledge - SynthType::Object => { - json!({ - "type": "object", - }) - } - }; - - type_attribute_variants.insert(attribute.clone(), type_attribute_variant); - } - - let name = format!("{}_attributes", type_name_lower); - let schema = serde_json::to_value(type_attribute_variants)?; - let collection = DomainCollection::new(name, schema); - - Ok(Collection::Generator(Generator::DomainCollection( - collection, - ))) + let mut type_attribute_variants: BTreeMap = maplit::btreemap! { + "type".to_string() => json!("object"), + }; + + for (AttributeType(attribute), r#type) in attributes { + let type_attribute_variant = match r#type { + SynthType::String => { + json!({ + "type": "string", + "faker": { + "generator": "bs_noun" + } + }) + }, + SynthType::Number => { + json!({ + "type": "number", + "subtype": "u32" + }) + }, + SynthType::Bool => { + json!({ + "type": "bool", + "frequency": 0.5 + }) + }, + // Object will be an empty object. + // This is something that could be tweaked on a case by case basis given some domain + // knowledge + SynthType::Object => { + json!({ + "type": "object", + }) + }, + }; + + type_attribute_variants.insert(attribute.clone(), type_attribute_variant); + } + + let name = format!("{}_attributes", type_name_lower); + let schema = serde_json::to_value(type_attribute_variants)?; + let collection = DomainCollection::new(name, schema); + + Ok(Collection::Generator(Generator::DomainCollection(collection))) } fn generate_schema( - types_attributes: &BTreeMap>, + types_attributes: &BTreeMap>, ) -> Result, ChronicleSynthError> { - let mut collections = Vec::new(); + let mut collections = Vec::new(); - for (r#type, attributes) in types_attributes { - let collection1 = domain_type_id_for_domain(r#type); - collections.push(collection1); + for (r#type, attributes) in types_attributes { + let collection1 = domain_type_id_for_domain(r#type); + collections.push(collection1); - let type_name_lower = r#type.as_str().to_lowercase(); + let type_name_lower = r#type.as_str().to_lowercase(); - let collection2 = set_attributes(&type_name_lower); - collections.push(collection2); + let collection2 = set_attributes(&type_name_lower); + collections.push(collection2); - let collection3 = type_attribute_variants(&type_name_lower, attributes)?; - collections.push(collection3); - } - Ok(collections) + let collection3 = type_attribute_variants(&type_name_lower, attributes)?; + collections.push(collection3); + } + Ok(collections) } #[cfg(test)] mod tests { - use crate::collection::CollectionHandling; + use crate::collection::CollectionHandling; - use super::*; - use maplit::btreemap; + use super::*; + use maplit::btreemap; - #[test] - fn test_type_attribute_variants() { - // Create a sample attribute map with two attributes - let attributes = btreemap! { - AttributeType("TestAttribute1".to_owned()) => SynthType::String, - AttributeType("TestAttribute2".to_owned()) => SynthType::Number, - }; + #[test] + fn test_type_attribute_variants() { + // Create a sample attribute map with two attributes + let attributes = btreemap! { + AttributeType("TestAttribute1".to_owned()) => SynthType::String, + AttributeType("TestAttribute2".to_owned()) => SynthType::Number, + }; - // Call the function being tested - let result = type_attribute_variants("test_type", &attributes).unwrap(); + // Call the function being tested + let result = type_attribute_variants("test_type", &attributes).unwrap(); - // Assert that the function returns a Collection with the expected properties - insta::assert_json_snapshot!(result.json_schema().unwrap().to_string(), @r###""{\"TestAttribute1\":{\"faker\":{\"generator\":\"bs_noun\"},\"type\":\"string\"},\"TestAttribute2\":{\"subtype\":\"u32\",\"type\":\"number\"},\"type\":\"object\"}""###); - } + // Assert that the function returns a Collection with the expected properties + insta::assert_json_snapshot!(result.json_schema().unwrap().to_string(), @r###""{\"TestAttribute1\":{\"faker\":{\"generator\":\"bs_noun\"},\"type\":\"string\"},\"TestAttribute2\":{\"subtype\":\"u32\",\"type\":\"number\"},\"type\":\"object\"}""###); + } } diff --git a/crates/chronicle-synth/src/error.rs b/crates/chronicle-synth/src/error.rs index d9ac074b9..a0cc8a833 100644 --- a/crates/chronicle-synth/src/error.rs +++ b/crates/chronicle-synth/src/error.rs @@ -2,15 +2,15 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum ChronicleSynthError { - #[error("Chronicle domain parsing error: {0}")] - ModelError(#[from] chronicle::codegen::model::ModelError), + #[error("Chronicle domain parsing error: {0}")] + ModelError(#[from] chronicle::codegen::model::ModelError), - #[error("Invalid JSON: {0}")] - JsonError(#[from] serde_json::Error), + #[error("Invalid JSON: {0}")] + JsonError(#[from] serde_json::Error), - #[error("I/O error: {0}")] - IO(#[from] std::io::Error), + #[error("I/O error: {0}")] + IO(#[from] std::io::Error), - #[error("YAML parsing error: {0}")] - YamlError(#[from] serde_yaml::Error), + #[error("YAML parsing error: {0}")] + YamlError(#[from] serde_yaml::Error), } diff --git a/crates/chronicle-synth/src/generate.rs b/crates/chronicle-synth/src/generate.rs index 1e4df2eb6..4fa00c9e2 100644 --- a/crates/chronicle-synth/src/generate.rs +++ b/crates/chronicle-synth/src/generate.rs @@ -1,16 +1,16 @@ //! A command-line interface for generating Chronicle Synth schema for a given domain. use std::{ - fs::File, - io::{BufReader, Write}, - path::{Path, PathBuf}, + fs::File, + io::{BufReader, Write}, + path::{Path, PathBuf}, }; use chronicle::codegen::linter::check_files; use chronicle_synth::{ - collection::{Collection, CollectionHandling}, - domain::TypesAttributesRoles, - error::ChronicleSynthError, + collection::{Collection, CollectionHandling}, + domain::TypesAttributesRoles, + error::ChronicleSynthError, }; use clap::StructOpt; use owo_colors::OwoColorize; @@ -18,66 +18,67 @@ use serde::{Deserialize, Serialize}; #[derive(StructOpt)] #[structopt( - name = "chronicle-domain-synth", - about = "Generate Chronicle Synth schema for your domain", - author = "Blockchain Technology Partners" + name = "chronicle-domain-synth", + about = "Generate Chronicle Synth schema for your domain", + author = "Blockchain Technology Partners" )] struct Cli { - #[structopt( - value_name = "FILE", - help = "Chronicle domain definition file", - parse(from_os_str), - default_value = "crates/chronicle-synth/domain.yaml" - )] - domain_file: PathBuf, + #[structopt( + value_name = "FILE", + help = "Chronicle domain definition file", + parse(from_os_str), + default_value = "crates/chronicle-synth/domain.yaml" + )] + domain_file: PathBuf, } const COLLECT_SCRIPT: &str = "./crates/chronicle-synth/collect"; fn main() -> Result<(), ChronicleSynthError> { - let args = Cli::from_args(); + let args = Cli::from_args(); - let domain_file = args.domain_file.as_path(); + let domain_file = args.domain_file.as_path(); - // Use Chronicle Domain Linter to check the domain definition file - let filenames = vec![domain_file.to_str().ok_or_else(|| { - ChronicleSynthError::IO(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Invalid path argument", - )) - })?]; + // Use Chronicle Domain Linter to check the domain definition file + let filenames = vec![domain_file.to_str().ok_or_else(|| { + ChronicleSynthError::IO(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Invalid path argument", + )) + })?]; - check_files(filenames); + check_files(filenames); - println!("{}", "No domain definition errors detected.".green()); + println!("{}", "No domain definition errors detected.".green()); - generate_synth_collections(domain_file)?; + generate_synth_collections(domain_file)?; - // Run the `collect` script to collate the complete set of Synth collections for the domain - let output = std::process::Command::new("bash") - .args([COLLECT_SCRIPT]) - .output() - .expect("Failed to execute 'collect' command"); + // Run the `collect` script to collate the complete set of Synth collections for the domain + let output = std::process::Command::new("bash") + .args([COLLECT_SCRIPT]) + .output() + .expect("Failed to execute 'collect' command"); - println!("{}", String::from_utf8_lossy(&output.stdout)); + println!("{}", String::from_utf8_lossy(&output.stdout)); - println!( - "{} contains the additional Synth collections generated for your domain.", - "crates/chronicle-synth/domain-schema/".underline() - ); - println!( - "The complete set of Synth collections for your domain can be found in '{}'.", - "crates/chronicle-synth/collections/".underline() - ); + println!( + "{} contains the additional Synth collections generated for your domain.", + "crates/chronicle-synth/domain-schema/".underline() + ); + println!( + "The complete set of Synth collections for your domain can be found in '{}'.", + "crates/chronicle-synth/collections/".underline() + ); - Ok(()) + Ok(()) } /// Generates Synth collections for the given domain definition file. /// /// This function takes a path to a domain definition file, generates Synth collections based on the -/// definition, and writes the resulting schema files to the `domain-schema` directory. Collections marked -/// as not being "chronicle operations" are written to a file called `exclude_collections.txt`. +/// definition, and writes the resulting schema files to the `domain-schema` directory. Collections +/// marked as not being "chronicle operations" are written to a file called +/// `exclude_collections.txt`. /// /// # Arguments /// @@ -98,27 +99,24 @@ fn main() -> Result<(), ChronicleSynthError> { /// } /// ``` fn generate_synth_collections(domain_file: &Path) -> Result<(), ChronicleSynthError> { - let generator = TypesAttributesRoles::from_file(domain_file)?; - println!( - "Generating schema for domain: {}.", - generator.name.underline() - ); - - let dir_path = PathBuf::from(DOMAIN_SCHEMA_TARGET_DIRECTORY); - std::fs::create_dir_all(&dir_path)?; - - let collections = generator.generate_domain_collections()?; - for collection in collections { - write_collection(&collection, &dir_path)?; - - match collection { - Collection::Operation(_) => {} - Collection::Generator(collection) => { - append_to_exclude_list(EXCLUDE_LIST, &collection.name())?; - } - } - } - Ok(()) + let generator = TypesAttributesRoles::from_file(domain_file)?; + println!("Generating schema for domain: {}.", generator.name.underline()); + + let dir_path = PathBuf::from(DOMAIN_SCHEMA_TARGET_DIRECTORY); + std::fs::create_dir_all(&dir_path)?; + + let collections = generator.generate_domain_collections()?; + for collection in collections { + write_collection(&collection, &dir_path)?; + + match collection { + Collection::Operation(_) => {}, + Collection::Generator(collection) => { + append_to_exclude_list(EXCLUDE_LIST, &collection.name())?; + }, + } + } + Ok(()) } const DOMAIN_SCHEMA_TARGET_DIRECTORY: &str = "./crates/chronicle-synth/domain-schema"; @@ -127,86 +125,87 @@ const EXCLUDE_LIST: &str = "./crates/chronicle-synth/exclude_collections.json"; #[derive(Deserialize, Serialize)] struct ExcludeCollections { - exclude: Vec, + exclude: Vec, } impl ExcludeCollections { - fn from_file(filename: impl AsRef) -> Result { - let file = File::open(filename)?; - let reader = BufReader::new(file); - let exclude_collections = serde_json::from_reader(reader)?; - Ok(exclude_collections) - } + fn from_file(filename: impl AsRef) -> Result { + let file = File::open(filename)?; + let reader = BufReader::new(file); + let exclude_collections = serde_json::from_reader(reader)?; + Ok(exclude_collections) + } } fn write_collection(collection: &Collection, dir_path: &Path) -> Result<(), ChronicleSynthError> { - let file_path = dir_path.join(collection.path()); - let mut file = File::create(file_path)?; - let schema = collection.json_schema()?; - file.write_all(serde_json::to_string(&schema)?.as_bytes())?; - Ok(()) + let file_path = dir_path.join(collection.path()); + let mut file = File::create(file_path)?; + let schema = collection.json_schema()?; + file.write_all(serde_json::to_string(&schema)?.as_bytes())?; + Ok(()) } /// Appends a collection name to the "exclude list" file, a list of collection files to be ignored -/// when generating the domain schema. See `generate` script in this repository for more information. +/// when generating the domain schema. See `generate` script in this repository for more +/// information. fn append_to_exclude_list( - path: impl AsRef, - collection: &str, + path: impl AsRef, + collection: &str, ) -> Result<(), ChronicleSynthError> { - let collection = collection.to_string(); - let mut list = ExcludeCollections::from_file(&path)?; + let collection = collection.to_string(); + let mut list = ExcludeCollections::from_file(&path)?; - if list.exclude.contains(&collection) { - return Ok(()); - } else { - list.exclude.push(collection); - } + if list.exclude.contains(&collection) { + return Ok(()) + } else { + list.exclude.push(collection); + } - let mut file = File::create(&path)?; - file.write_all(serde_json::to_string_pretty(&list)?.as_bytes())?; + let mut file = File::create(&path)?; + file.write_all(serde_json::to_string_pretty(&list)?.as_bytes())?; - Ok(()) + Ok(()) } #[cfg(test)] mod tests { - use super::*; - use std::{fs::File, io::BufReader}; + use super::*; + use std::{fs::File, io::BufReader}; - use assert_fs::prelude::*; + use assert_fs::prelude::*; - const PATH: &str = "test_exclude_collections.json"; + const PATH: &str = "test_exclude_collections.json"; - fn create_test_exclude_collections( - ) -> Result> { - let file = assert_fs::NamedTempFile::new(PATH)?; + fn create_test_exclude_collections( + ) -> Result> { + let file = assert_fs::NamedTempFile::new(PATH)?; - file.write_str( - r#" + file.write_str( + r#" { "exclude": [ "already_ignore_this" ] } "#, - )?; + )?; - Ok(file) - } + Ok(file) + } - #[test] - fn test_append_to_exclude_list() -> Result<(), ChronicleSynthError> { - let file = create_test_exclude_collections().unwrap(); + #[test] + fn test_append_to_exclude_list() -> Result<(), ChronicleSynthError> { + let file = create_test_exclude_collections().unwrap(); - // Call the function to append to the exclude list - append_to_exclude_list(file.path(), "ignore_this_collection_when_printing")?; + // Call the function to append to the exclude list + append_to_exclude_list(file.path(), "ignore_this_collection_when_printing")?; - // Read the contents of the file and check if the collection was added - let file = File::open(file.path())?; - let reader = BufReader::new(file); - let exclude_collections: ExcludeCollections = serde_json::from_reader(reader)?; + // Read the contents of the file and check if the collection was added + let file = File::open(file.path())?; + let reader = BufReader::new(file); + let exclude_collections: ExcludeCollections = serde_json::from_reader(reader)?; - insta::assert_json_snapshot!(exclude_collections, @r###" + insta::assert_json_snapshot!(exclude_collections, @r###" { "exclude": [ "already_ignore_this", @@ -214,29 +213,29 @@ mod tests { ] }"###); - Ok(()) - } + Ok(()) + } - #[test] - fn test_append_to_exclude_list_skips_collections_already_on_list( - ) -> Result<(), ChronicleSynthError> { - let file = create_test_exclude_collections().unwrap(); + #[test] + fn test_append_to_exclude_list_skips_collections_already_on_list( + ) -> Result<(), ChronicleSynthError> { + let file = create_test_exclude_collections().unwrap(); - // Call the function to append to the exclude list - append_to_exclude_list(file.path(), "already_ignore_this")?; + // Call the function to append to the exclude list + append_to_exclude_list(file.path(), "already_ignore_this")?; - // Read the contents of the file and check if the collection was added - let file = File::open(file.path())?; - let reader = BufReader::new(file); - let exclude_collections: ExcludeCollections = serde_json::from_reader(reader)?; + // Read the contents of the file and check if the collection was added + let file = File::open(file.path())?; + let reader = BufReader::new(file); + let exclude_collections: ExcludeCollections = serde_json::from_reader(reader)?; - insta::assert_json_snapshot!(exclude_collections, @r###" + insta::assert_json_snapshot!(exclude_collections, @r###" { "exclude": [ "already_ignore_this" ] }"###); - Ok(()) - } + Ok(()) + } } diff --git a/crates/chronicle-telemetry/src/telemetry.rs b/crates/chronicle-telemetry/src/telemetry.rs index eb7b09fff..d0d9dc34d 100644 --- a/crates/chronicle-telemetry/src/telemetry.rs +++ b/crates/chronicle-telemetry/src/telemetry.rs @@ -6,97 +6,89 @@ use url::Url; #[derive(Debug, Clone, Copy)] pub enum ConsoleLogging { - Off, - Pretty, - Json, + Off, + Pretty, + Json, } #[cfg(feature = "tokio-tracing")] macro_rules! console_layer { - () => { - console_subscriber::ConsoleLayer::builder() - .with_default_env() - .spawn() - }; + () => { + console_subscriber::ConsoleLayer::builder().with_default_env().spawn() + }; } macro_rules! stdio_layer { - () => { - tracing_subscriber::fmt::layer() - .with_level(true) - .with_target(true) - .with_thread_ids(true) - }; + () => { + tracing_subscriber::fmt::layer() + .with_level(true) + .with_target(true) + .with_thread_ids(true) + }; } macro_rules! apm_layer { - ( $address: expr ) => { - tracing_elastic_apm::new_layer( - "chronicle".to_string(), - // remember to use desired protocol below, e.g. http:// - Config::new($address.to_string()), - ) - .unwrap() - }; + ( $address: expr ) => { + tracing_elastic_apm::new_layer( + "chronicle".to_string(), + // remember to use desired protocol below, e.g. http:// + Config::new($address.to_string()), + ) + .unwrap() + }; } pub fn telemetry(collector_endpoint: Option, console_logging: ConsoleLogging) { - LogTracer::init_with_filter(LevelFilter::Trace).ok(); + LogTracer::init_with_filter(LevelFilter::Trace).ok(); - let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("error")); - match (collector_endpoint, console_logging) { - (Some(otel), ConsoleLogging::Json) => { - set_global_default( - Registry::default() - .with(env_filter) - .with(apm_layer!(otel)) - .with(stdio_layer!().json()), - ) - .ok(); - } - (Some(otel), ConsoleLogging::Pretty) => { - set_global_default( - Registry::default() - .with(env_filter) - .with(apm_layer!(otel.as_str())) - .with(stdio_layer!().pretty()), - ) - .ok(); - } - (Some(otel), ConsoleLogging::Off) => { - set_global_default( - Registry::default() - .with(env_filter) - .with(apm_layer!(otel.as_str())), - ) - .ok(); - } - (None, ConsoleLogging::Json) => { - set_global_default( - Registry::default() - .with(env_filter) - .with(stdio_layer!().json()), - ) - .ok(); - } - (None, ConsoleLogging::Pretty) => { - cfg_if::cfg_if! { - if #[cfg(feature = "tokio-tracing")] { - let layers = Registry::default() - .with(env_filter) - .with(stdio_layer!().pretty()) - .with(console_layer!()); + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("error")); + match (collector_endpoint, console_logging) { + (Some(otel), ConsoleLogging::Json) => { + set_global_default( + Registry::default() + .with(env_filter) + .with(apm_layer!(otel)) + .with(stdio_layer!().json()), + ) + .ok(); + }, + (Some(otel), ConsoleLogging::Pretty) => { + set_global_default( + Registry::default() + .with(env_filter) + .with(apm_layer!(otel.as_str())) + .with(stdio_layer!().pretty()), + ) + .ok(); + }, + (Some(otel), ConsoleLogging::Off) => { + set_global_default( + Registry::default().with(env_filter).with(apm_layer!(otel.as_str())), + ) + .ok(); + }, + (None, ConsoleLogging::Json) => { + set_global_default(Registry::default().with(env_filter).with(stdio_layer!().json())) + .ok(); + }, + (None, ConsoleLogging::Pretty) => { + cfg_if::cfg_if! { + if #[cfg(feature = "tokio-tracing")] { + let layers = Registry::default() + .with(env_filter) + .with(stdio_layer!().pretty()) + .with(console_layer!()); - set_global_default(layers).ok(); + set_global_default(layers).ok(); - } else { - let layers = Registry::default() - .with(env_filter) - .with(stdio_layer!().pretty()); - set_global_default(layers).ok(); - } - } - } - _ => (), - } + } else { + let layers = Registry::default() + .with(env_filter) + .with(stdio_layer!().pretty()); + set_global_default(layers).ok(); + } + } + }, + _ => (), + } } diff --git a/crates/chronicle/src/bootstrap/cli.rs b/crates/chronicle/src/bootstrap/cli.rs index e1785d113..d7a934c78 100644 --- a/crates/chronicle/src/bootstrap/cli.rs +++ b/crates/chronicle/src/bootstrap/cli.rs @@ -4,18 +4,18 @@ use api::ApiError; use chronicle_protocol::async_stl_client::error::SawtoothCommunicationError; use chronicle_signing::SecretError; use clap::{ - builder::{PossibleValuesParser, StringValueParser}, - *, + builder::{PossibleValuesParser, StringValueParser}, + *, }; use common::{ - attributes::{Attribute, Attributes}, - commands::{ActivityCommand, AgentCommand, ApiCommand, EntityCommand}, - import::FromUrlError, - opa::{OpaExecutorError, PolicyLoaderError}, - prov::{ - operations::DerivationType, ActivityId, AgentId, CompactionError, DomaintypeId, EntityId, - ExternalId, ExternalIdPart, ParseIriError, - }, + attributes::{Attribute, Attributes}, + commands::{ActivityCommand, AgentCommand, ApiCommand, EntityCommand}, + import::FromUrlError, + opa::{OpaExecutorError, PolicyLoaderError}, + prov::{ + operations::DerivationType, ActivityId, AgentId, CompactionError, DomaintypeId, EntityId, + ExternalId, ExternalIdPart, ParseIriError, + }, }; use iref::Iri; use thiserror::Error; @@ -24,312 +24,289 @@ use tracing::info; use user_error::UFE; use crate::{ - codegen::{ - ActivityDef, AgentDef, AttributeDef, ChronicleDomainDef, CliName, EntityDef, TypeName, - }, - PrimitiveType, + codegen::{ + ActivityDef, AgentDef, AttributeDef, ChronicleDomainDef, CliName, EntityDef, TypeName, + }, + PrimitiveType, }; #[derive(Debug, Error)] pub enum CliError { - #[error("Missing argument: {arg}")] - MissingArgument { arg: String }, + #[error("Missing argument: {arg}")] + MissingArgument { arg: String }, - #[error("Invalid argument {arg} expected {expected} got {got}")] - InvalidArgument { - arg: String, - expected: String, - got: String, - }, + #[error("Invalid argument {arg} expected {expected} got {got}")] + InvalidArgument { arg: String, expected: String, got: String }, - #[error("Bad argument: {0}")] - ArgumentParsing(#[from] clap::Error), + #[error("Bad argument: {0}")] + ArgumentParsing(#[from] clap::Error), - #[error("Invalid IRI: {0}")] - InvalidIri(#[from] iref::Error), + #[error("Invalid IRI: {0}")] + InvalidIri(#[from] iref::Error), - #[error("Invalid Chronicle IRI: {0}")] - InvalidChronicleIri(#[from] ParseIriError), + #[error("Invalid Chronicle IRI: {0}")] + InvalidChronicleIri(#[from] ParseIriError), - #[error("Invalid JSON: {0}")] - InvalidJson(#[from] serde_json::Error), + #[error("Invalid JSON: {0}")] + InvalidJson(#[from] serde_json::Error), - #[error("Invalid URI: {0}")] - InvalidUri(#[from] url::ParseError), + #[error("Invalid URI: {0}")] + InvalidUri(#[from] url::ParseError), - #[error("Invalid timestamp: {0}")] - InvalidTimestamp(#[from] chrono::ParseError), + #[error("Invalid timestamp: {0}")] + InvalidTimestamp(#[from] chrono::ParseError), - #[error("Invalid coercion: {arg}")] - InvalidCoercion { arg: String }, + #[error("Invalid coercion: {arg}")] + InvalidCoercion { arg: String }, - #[error("API failure: {0}")] - ApiError(#[from] ApiError), + #[error("API failure: {0}")] + ApiError(#[from] ApiError), - #[error("Secrets : {0}")] - Secrets(#[from] SecretError), + #[error("Secrets : {0}")] + Secrets(#[from] SecretError), - #[error("IO error: {0}")] - InputOutput(#[from] std::io::Error), + #[error("IO error: {0}")] + InputOutput(#[from] std::io::Error), - #[error("Invalid configuration file: {0}")] - ConfigInvalid(#[from] toml::de::Error), + #[error("Invalid configuration file: {0}")] + ConfigInvalid(#[from] toml::de::Error), - #[error("Invalid path: {path}")] - InvalidPath { path: String }, + #[error("Invalid path: {path}")] + InvalidPath { path: String }, - #[error("Invalid JSON-LD: {0}")] - Ld(#[from] CompactionError), + #[error("Invalid JSON-LD: {0}")] + Ld(#[from] CompactionError), - #[error("Failure in commit notification stream: {0}")] - CommitNoticiationStream(#[from] RecvError), + #[error("Failure in commit notification stream: {0}")] + CommitNoticiationStream(#[from] RecvError), - #[error("Policy loader error: {0}")] - OpaPolicyLoader(#[from] PolicyLoaderError), + #[error("Policy loader error: {0}")] + OpaPolicyLoader(#[from] PolicyLoaderError), - #[error("OPA executor error: {0}")] - OpaExecutor(#[from] OpaExecutorError), + #[error("OPA executor error: {0}")] + OpaExecutor(#[from] OpaExecutorError), - #[error("Sawtooth communication error: {source}")] - SawtoothCommunicationError { - #[from] - source: SawtoothCommunicationError, - }, + #[error("Sawtooth communication error: {source}")] + SawtoothCommunicationError { + #[from] + source: SawtoothCommunicationError, + }, - #[error("Error loading from URL: {0}")] - UrlError(#[from] FromUrlError), + #[error("Error loading from URL: {0}")] + UrlError(#[from] FromUrlError), - #[error("UTF-8 error: {0}")] - Utf8Error(#[from] std::str::Utf8Error), + #[error("UTF-8 error: {0}")] + Utf8Error(#[from] std::str::Utf8Error), } impl CliError { - pub fn missing_argument(arg: impl Into) -> Self { - Self::MissingArgument { arg: arg.into() } - } + pub fn missing_argument(arg: impl Into) -> Self { + Self::MissingArgument { arg: arg.into() } + } } /// Ugly but we need this until ! is stable, see impl From for CliError { - fn from(_: Infallible) -> Self { - unreachable!() - } + fn from(_: Infallible) -> Self { + unreachable!() + } } impl UFE for CliError {} pub(crate) trait SubCommand { - fn as_cmd(&self) -> Command; - fn matches(&self, matches: &ArgMatches) -> Result, CliError>; + fn as_cmd(&self) -> Command; + fn matches(&self, matches: &ArgMatches) -> Result, CliError>; } pub struct AttributeCliModel { - pub attribute: AttributeDef, - pub attribute_name: String, - pub attribute_help: String, + pub attribute: AttributeDef, + pub attribute_name: String, + pub attribute_help: String, } impl AttributeCliModel { - pub fn new(attribute: AttributeDef) -> Self { - Self { - attribute_name: format!("{}-attr", attribute.as_cli_name()), - attribute_help: format!("The value of the {} attribute", attribute.as_type_name()), - attribute, - } - } - - pub fn as_arg(&self) -> Arg { - Arg::new(&*self.attribute_name) - .long(&self.attribute_name) - .help(&*self.attribute_help) - .takes_value(true) - .required(true) - } + pub fn new(attribute: AttributeDef) -> Self { + Self { + attribute_name: format!("{}-attr", attribute.as_cli_name()), + attribute_help: format!("The value of the {} attribute", attribute.as_type_name()), + attribute, + } + } + + pub fn as_arg(&self) -> Arg { + Arg::new(&*self.attribute_name) + .long(&self.attribute_name) + .help(&*self.attribute_help) + .takes_value(true) + .required(true) + } } pub struct AgentCliModel { - pub agent: AgentDef, - pub attributes: Vec, - pub about: String, - pub define_about: String, - pub external_id: String, + pub agent: AgentDef, + pub attributes: Vec, + pub about: String, + pub define_about: String, + pub external_id: String, } impl AgentCliModel { - pub fn new(agent: &AgentDef) -> Self { - let attributes = agent - .attributes - .iter() - .map(|attr| AttributeCliModel::new(attr.clone())) - .collect(); - Self { + pub fn new(agent: &AgentDef) -> Self { + let attributes = agent + .attributes + .iter() + .map(|attr| AttributeCliModel::new(attr.clone())) + .collect(); + Self { agent: agent.clone(), attributes, external_id: agent.as_cli_name(), about: format!("Operations on {} agents", agent.as_type_name()), define_about: format!("Define an agent of type {} with the given external_id or IRI, redefinition with different attribute values is not allowed", agent.as_type_name()) } - } + } } fn name_from<'a, Id>( - args: &'a ArgMatches, - name_param: &str, - id_param: &str, + args: &'a ArgMatches, + name_param: &str, + id_param: &str, ) -> Result where - Id: 'a + TryFrom, Error = ParseIriError> + ExternalIdPart, + Id: 'a + TryFrom, Error = ParseIriError> + ExternalIdPart, { - if let Some(external_id) = args.get_one::(name_param) { - Ok(ExternalId::from(external_id)) - } else if let Some(id) = args.get_one::(id_param) { - let iri = Iri::from_str(id)?; - let id = Id::try_from(iri)?; - Ok(id.external_id_part().to_owned()) - } else { - Err(CliError::MissingArgument { - arg: format!("Missing {name_param} and {id_param}"), - }) - } + if let Some(external_id) = args.get_one::(name_param) { + Ok(ExternalId::from(external_id)) + } else if let Some(id) = args.get_one::(id_param) { + let iri = Iri::from_str(id)?; + let id = Id::try_from(iri)?; + Ok(id.external_id_part().to_owned()) + } else { + Err(CliError::MissingArgument { arg: format!("Missing {name_param} and {id_param}") }) + } } fn id_from<'a, Id>(args: &'a ArgMatches, id_param: &str) -> Result where - Id: 'a + TryFrom, Error = ParseIriError> + ExternalIdPart, + Id: 'a + TryFrom, Error = ParseIriError> + ExternalIdPart, { - if let Some(id) = args.get_one::(id_param) { - Ok(Id::try_from(Iri::from_str(id)?)?) - } else { - Err(CliError::MissingArgument { - arg: format!("Missing {id_param} "), - }) - } + if let Some(id) = args.get_one::(id_param) { + Ok(Id::try_from(Iri::from_str(id)?)?) + } else { + Err(CliError::MissingArgument { arg: format!("Missing {id_param} ") }) + } } fn id_from_option<'a, Id>(args: &'a ArgMatches, id_param: &str) -> Result, CliError> where - Id: 'a + TryFrom, Error = ParseIriError> + ExternalIdPart, + Id: 'a + TryFrom, Error = ParseIriError> + ExternalIdPart, { - match id_from(args, id_param) { - Err(CliError::MissingArgument { .. }) => Ok(None), - Err(e) => Err(e), - Ok(id) => Ok(Some(id)), - } + match id_from(args, id_param) { + Err(CliError::MissingArgument { .. }) => Ok(None), + Err(e) => Err(e), + Ok(id) => Ok(Some(id)), + } } fn namespace_from(args: &ArgMatches) -> Result { - if let Some(namespace) = args.get_one::("namespace") { - Ok(ExternalId::from(namespace)) - } else { - Err(CliError::MissingArgument { - arg: "namespace".to_owned(), - }) - } + if let Some(namespace) = args.get_one::("namespace") { + Ok(ExternalId::from(namespace)) + } else { + Err(CliError::MissingArgument { arg: "namespace".to_owned() }) + } } -/// Deserialize to a JSON value and ensure that it matches the specified primitive type, we need to force any bare literal text to be quoted -/// use of coercion afterwards will produce a proper json value type for non strings +/// Deserialize to a JSON value and ensure that it matches the specified primitive type, we need to +/// force any bare literal text to be quoted use of coercion afterwards will produce a proper json +/// value type for non strings fn attribute_value_from_param( - arg: &str, - value: &str, - typ: PrimitiveType, + arg: &str, + value: &str, + typ: PrimitiveType, ) -> Result { - let value = { - if !value.contains('"') { - format!(r#""{value}""#) - } else { - value.to_owned() - } - }; - - let mut value = serde_json::from_str(&value)?; - match typ { - PrimitiveType::Bool => { - if let Some(coerced) = valico::json_dsl::boolean() - .coerce(&mut value, ".") - .map_err(|_e| CliError::InvalidCoercion { - arg: arg.to_owned(), - })? - { - Ok(coerced) - } else { - Ok(value) - } - } - PrimitiveType::String => { - if let Some(coerced) = - valico::json_dsl::string() - .coerce(&mut value, ".") - .map_err(|_e| CliError::InvalidCoercion { - arg: arg.to_owned(), - })? - { - Ok(coerced) - } else { - Ok(value) - } - } - PrimitiveType::Int => { - if let Some(coerced) = - valico::json_dsl::i64() - .coerce(&mut value, ".") - .map_err(|_e| CliError::InvalidCoercion { - arg: arg.to_owned(), - })? - { - Ok(coerced) - } else { - Ok(value) - } - } - PrimitiveType::JSON => { - if let Some(coerced) = - valico::json_dsl::object() - .coerce(&mut value, ".") - .map_err(|_e| CliError::InvalidCoercion { - arg: arg.to_owned(), - })? - { - Ok(coerced) - } else { - Ok(value) - } - } - } + let value = { + if !value.contains('"') { + format!(r#""{value}""#) + } else { + value.to_owned() + } + }; + + let mut value = serde_json::from_str(&value)?; + match typ { + PrimitiveType::Bool => { + if let Some(coerced) = valico::json_dsl::boolean() + .coerce(&mut value, ".") + .map_err(|_e| CliError::InvalidCoercion { arg: arg.to_owned() })? + { + Ok(coerced) + } else { + Ok(value) + } + }, + PrimitiveType::String => { + if let Some(coerced) = valico::json_dsl::string() + .coerce(&mut value, ".") + .map_err(|_e| CliError::InvalidCoercion { arg: arg.to_owned() })? + { + Ok(coerced) + } else { + Ok(value) + } + }, + PrimitiveType::Int => { + if let Some(coerced) = valico::json_dsl::i64() + .coerce(&mut value, ".") + .map_err(|_e| CliError::InvalidCoercion { arg: arg.to_owned() })? + { + Ok(coerced) + } else { + Ok(value) + } + }, + PrimitiveType::JSON => { + if let Some(coerced) = valico::json_dsl::object() + .coerce(&mut value, ".") + .map_err(|_e| CliError::InvalidCoercion { arg: arg.to_owned() })? + { + Ok(coerced) + } else { + Ok(value) + } + }, + } } fn attributes_from( - args: &ArgMatches, - typ: impl AsRef, - attributes: &[AttributeCliModel], + args: &ArgMatches, + typ: impl AsRef, + attributes: &[AttributeCliModel], ) -> Result { - Ok(Attributes { - typ: Some(DomaintypeId::from_external_id(typ)), - attributes: attributes - .iter() - .map(|attr| { - let value = attribute_value_from_param( - &attr.attribute_name, - args.get_one::(&attr.attribute_name).unwrap(), - attr.attribute.primitive_type, - )?; - Ok::<_, CliError>(( - attr.attribute.as_type_name(), - Attribute { - typ: attr.attribute.as_type_name(), - value, - }, - )) - }) - .collect::, _>>()?, - }) + Ok(Attributes { + typ: Some(DomaintypeId::from_external_id(typ)), + attributes: attributes + .iter() + .map(|attr| { + let value = attribute_value_from_param( + &attr.attribute_name, + args.get_one::(&attr.attribute_name).unwrap(), + attr.attribute.primitive_type, + )?; + Ok::<_, CliError>(( + attr.attribute.as_type_name(), + Attribute { typ: attr.attribute.as_type_name(), value }, + )) + }) + .collect::, _>>()?, + }) } impl SubCommand for AgentCliModel { - fn as_cmd(&self) -> Command { - let cmd = Command::new(&*self.external_id).about(&*self.about); + fn as_cmd(&self) -> Command { + let cmd = Command::new(&*self.external_id).about(&*self.about); - let mut define = Command::new("define") + let mut define = Command::new("define") .about(&*self.define_about) .arg(Arg::new("external_id") .help("An externally meaningful identifier for the agent, e.g. a URI or relational id") @@ -350,80 +327,80 @@ impl SubCommand for AgentCliModel { .takes_value(true), ); - for attr in &self.attributes { - define = define.arg(attr.as_arg()); - } - - cmd.subcommand(define).subcommand( - Command::new("use") - .about("Make the specified agent the context for activities and entities") - .arg( - Arg::new("id") - .help("A valid chronicle agent IRI") - .required(true) - .takes_value(true), - ) - .arg( - Arg::new("namespace") - .short('n') - .long("namespace") - .default_value("default") - .required(false) - .takes_value(true), - ), - ) - } - - fn matches(&self, matches: &ArgMatches) -> Result, CliError> { - if let Some(matches) = matches.subcommand_matches("define") { - return Ok(Some(ApiCommand::Agent(AgentCommand::Create { - external_id: name_from::(matches, "external_id", "id")?, - namespace: namespace_from(matches)?, - attributes: attributes_from(matches, &self.agent.external_id, &self.attributes)?, - }))); - } - - if let Some(matches) = matches.subcommand_matches("use") { - return Ok(Some(ApiCommand::Agent(AgentCommand::UseInContext { - id: id_from(matches, "id")?, - namespace: namespace_from(matches)?, - }))); - }; - - Ok(None) - } + for attr in &self.attributes { + define = define.arg(attr.as_arg()); + } + + cmd.subcommand(define).subcommand( + Command::new("use") + .about("Make the specified agent the context for activities and entities") + .arg( + Arg::new("id") + .help("A valid chronicle agent IRI") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("namespace") + .short('n') + .long("namespace") + .default_value("default") + .required(false) + .takes_value(true), + ), + ) + } + + fn matches(&self, matches: &ArgMatches) -> Result, CliError> { + if let Some(matches) = matches.subcommand_matches("define") { + return Ok(Some(ApiCommand::Agent(AgentCommand::Create { + external_id: name_from::(matches, "external_id", "id")?, + namespace: namespace_from(matches)?, + attributes: attributes_from(matches, &self.agent.external_id, &self.attributes)?, + }))) + } + + if let Some(matches) = matches.subcommand_matches("use") { + return Ok(Some(ApiCommand::Agent(AgentCommand::UseInContext { + id: id_from(matches, "id")?, + namespace: namespace_from(matches)?, + }))) + }; + + Ok(None) + } } pub struct ActivityCliModel { - pub activity: ActivityDef, - pub attributes: Vec, - pub about: String, - pub define_about: String, - pub external_id: String, + pub activity: ActivityDef, + pub attributes: Vec, + pub about: String, + pub define_about: String, + pub external_id: String, } impl ActivityCliModel { - fn new(activity: &ActivityDef) -> Self { - let attributes = activity - .attributes - .iter() - .map(|attr| AttributeCliModel::new(attr.clone())) - .collect(); - Self { + fn new(activity: &ActivityDef) -> Self { + let attributes = activity + .attributes + .iter() + .map(|attr| AttributeCliModel::new(attr.clone())) + .collect(); + Self { activity: activity.clone(), attributes, external_id: activity.as_cli_name(), about: format!("Operations on {} activities", activity.as_type_name()), define_about: format!("Define an activity of type {} with the given external_id or IRI, redefinition with different attribute values is not allowed", activity.as_type_name()), } - } + } } impl SubCommand for ActivityCliModel { - fn as_cmd(&self) -> Command { - let cmd = Command::new(&*self.external_id).about(&*self.about); + fn as_cmd(&self) -> Command { + let cmd = Command::new(&*self.external_id).about(&*self.about); - let mut define = + let mut define = Command::new("define") .about(&*self.define_about) .arg(Arg::new("external_id") @@ -445,11 +422,11 @@ impl SubCommand for ActivityCliModel { .takes_value(true), ); - for attr in &self.attributes { - define = define.arg(attr.as_arg()); - } + for attr in &self.attributes { + define = define.arg(attr.as_arg()); + } - cmd.subcommand(define) + cmd.subcommand(define) .subcommand( Command::new("start") .about("Record this activity as started at the specified time, if no time is specified the current time is used") @@ -584,103 +561,94 @@ impl SubCommand for ActivityCliModel { .takes_value(true), ) ) - } - - fn matches(&self, matches: &ArgMatches) -> Result, CliError> { - if let Some(matches) = matches.subcommand_matches("define") { - return Ok(Some(ApiCommand::Activity(ActivityCommand::Create { - external_id: name_from::(matches, "external_id", "id")?, - namespace: namespace_from(matches)?, - attributes: attributes_from(matches, &self.activity.external_id, &self.attributes)?, - }))); - } - - if let Some(matches) = matches.subcommand_matches("start") { - return Ok(Some(ApiCommand::Activity(ActivityCommand::Start { - id: id_from(matches, "id")?, - namespace: namespace_from(matches)?, - time: matches - .get_one::("time") - .map(|t| t.parse()) - .transpose()?, - agent: id_from_option(matches, "agent_id")?, - }))); - }; - - if let Some(matches) = matches.subcommand_matches("end") { - return Ok(Some(ApiCommand::Activity(ActivityCommand::End { - id: id_from(matches, "id")?, - namespace: namespace_from(matches)?, - time: matches - .get_one::("time") - .map(|t| t.parse()) - .transpose()?, - agent: id_from_option(matches, "agent_id")?, - }))); - }; - - if let Some(matches) = matches.subcommand_matches("instant") { - return Ok(Some(ApiCommand::Activity(ActivityCommand::Instant { - id: id_from(matches, "id")?, - namespace: namespace_from(matches)?, - time: matches - .get_one::("time") - .map(|t| t.parse()) - .transpose()?, - agent: id_from_option(matches, "agent_id")?, - }))); - }; - - if let Some(matches) = matches.subcommand_matches("use") { - return Ok(Some(ApiCommand::Activity(ActivityCommand::Use { - id: id_from(matches, "entity_id")?, - namespace: namespace_from(matches)?, - activity: id_from(matches, "activity_id")?, - }))); - }; - - if let Some(matches) = matches.subcommand_matches("generate") { - return Ok(Some(ApiCommand::Activity(ActivityCommand::Generate { - id: id_from(matches, "entity_id")?, - namespace: namespace_from(matches)?, - activity: id_from(matches, "activity_id")?, - }))); - }; - - Ok(None) - } + } + + fn matches(&self, matches: &ArgMatches) -> Result, CliError> { + if let Some(matches) = matches.subcommand_matches("define") { + return Ok(Some(ApiCommand::Activity(ActivityCommand::Create { + external_id: name_from::(matches, "external_id", "id")?, + namespace: namespace_from(matches)?, + attributes: attributes_from(matches, &self.activity.external_id, &self.attributes)?, + }))) + } + + if let Some(matches) = matches.subcommand_matches("start") { + return Ok(Some(ApiCommand::Activity(ActivityCommand::Start { + id: id_from(matches, "id")?, + namespace: namespace_from(matches)?, + time: matches.get_one::("time").map(|t| t.parse()).transpose()?, + agent: id_from_option(matches, "agent_id")?, + }))) + }; + + if let Some(matches) = matches.subcommand_matches("end") { + return Ok(Some(ApiCommand::Activity(ActivityCommand::End { + id: id_from(matches, "id")?, + namespace: namespace_from(matches)?, + time: matches.get_one::("time").map(|t| t.parse()).transpose()?, + agent: id_from_option(matches, "agent_id")?, + }))) + }; + + if let Some(matches) = matches.subcommand_matches("instant") { + return Ok(Some(ApiCommand::Activity(ActivityCommand::Instant { + id: id_from(matches, "id")?, + namespace: namespace_from(matches)?, + time: matches.get_one::("time").map(|t| t.parse()).transpose()?, + agent: id_from_option(matches, "agent_id")?, + }))) + }; + + if let Some(matches) = matches.subcommand_matches("use") { + return Ok(Some(ApiCommand::Activity(ActivityCommand::Use { + id: id_from(matches, "entity_id")?, + namespace: namespace_from(matches)?, + activity: id_from(matches, "activity_id")?, + }))) + }; + + if let Some(matches) = matches.subcommand_matches("generate") { + return Ok(Some(ApiCommand::Activity(ActivityCommand::Generate { + id: id_from(matches, "entity_id")?, + namespace: namespace_from(matches)?, + activity: id_from(matches, "activity_id")?, + }))) + }; + + Ok(None) + } } pub struct EntityCliModel { - pub entity: EntityDef, - pub attributes: Vec, - pub about: String, - pub define_about: String, - pub external_id: String, + pub entity: EntityDef, + pub attributes: Vec, + pub about: String, + pub define_about: String, + pub external_id: String, } impl EntityCliModel { - pub fn new(entity: &EntityDef) -> Self { - let attributes = entity - .attributes - .iter() - .map(|attr| AttributeCliModel::new(attr.clone())) - .collect(); - Self { + pub fn new(entity: &EntityDef) -> Self { + let attributes = entity + .attributes + .iter() + .map(|attr| AttributeCliModel::new(attr.clone())) + .collect(); + Self { entity: entity.clone(), attributes, external_id: entity.as_cli_name(), about: format!("Operations on {} entities", entity.as_type_name()), define_about: format!("Define an entity of type {} with the given external_id or IRI, redefinition with different attribute values is not allowed", entity.as_type_name()), } - } + } } impl SubCommand for EntityCliModel { - fn as_cmd(&self) -> Command { - let cmd = Command::new(&self.external_id).about(&*self.about); + fn as_cmd(&self) -> Command { + let cmd = Command::new(&self.external_id).about(&*self.about); - let mut define = + let mut define = Command::new("define") .about(&*self.define_about) .arg(Arg::new("external_id") @@ -702,119 +670,115 @@ impl SubCommand for EntityCliModel { .takes_value(true), ); - for attr in &self.attributes { - define = define.arg(attr.as_arg()); - } - - cmd.subcommand(define).subcommand( - Command::new("derive") - .about("Derivation of entities from other entities") - .arg( - Arg::new("subtype") - .help("The derivation subtype") - .long("subtype") - .required(false) - .takes_value(true) - .value_parser(PossibleValuesParser::new([ - "revision", - "quotation", - "primary-source", - ])), - ) - .arg( - Arg::new("generated_entity_id") - .help("A valid chronicle entity IRI for the generated entity") - .takes_value(true) - .required(true), - ) - .arg( - Arg::new("used_entity_id") - .help("A valid chronicle entity IRI for the used entity") - .takes_value(true) - .required(true), - ) - .arg( - Arg::new("activity_id") - .help("The activity IRI that generated the entity") - .long("activity") - .takes_value(true) - .required(false), - ) - .arg( - Arg::new("namespace") - .short('n') - .long("namespace") - .default_value("default") - .required(false) - .takes_value(true), - ), - ) - } - - fn matches(&self, matches: &ArgMatches) -> Result, CliError> { - if let Some(matches) = matches.subcommand_matches("define") { - return Ok(Some(ApiCommand::Entity(EntityCommand::Create { - external_id: name_from::(matches, "external_id", "id")?, - namespace: namespace_from(matches)?, - attributes: attributes_from(matches, &self.entity.external_id, &self.attributes)?, - }))); - } - - if let Some(matches) = matches.subcommand_matches("derive") { - return Ok(Some(ApiCommand::Entity(EntityCommand::Derive { - namespace: namespace_from(matches)?, - id: id_from(matches, "generated_entity_id")?, - derivation: matches - .get_one::("subtype") - .map(|v| match v.as_str() { - "revision" => DerivationType::Revision, - "quotation" => DerivationType::Quotation, - "primary-source" => DerivationType::PrimarySource, - _ => unreachable!(), // Guaranteed by PossibleValuesParser - }) - .unwrap_or(DerivationType::None), - activity: id_from_option(matches, "activity_id")?, - used_entity: id_from(matches, "used_entity_id")?, - }))); - } - - Ok(None) - } + for attr in &self.attributes { + define = define.arg(attr.as_arg()); + } + + cmd.subcommand(define).subcommand( + Command::new("derive") + .about("Derivation of entities from other entities") + .arg( + Arg::new("subtype") + .help("The derivation subtype") + .long("subtype") + .required(false) + .takes_value(true) + .value_parser(PossibleValuesParser::new([ + "revision", + "quotation", + "primary-source", + ])), + ) + .arg( + Arg::new("generated_entity_id") + .help("A valid chronicle entity IRI for the generated entity") + .takes_value(true) + .required(true), + ) + .arg( + Arg::new("used_entity_id") + .help("A valid chronicle entity IRI for the used entity") + .takes_value(true) + .required(true), + ) + .arg( + Arg::new("activity_id") + .help("The activity IRI that generated the entity") + .long("activity") + .takes_value(true) + .required(false), + ) + .arg( + Arg::new("namespace") + .short('n') + .long("namespace") + .default_value("default") + .required(false) + .takes_value(true), + ), + ) + } + + fn matches(&self, matches: &ArgMatches) -> Result, CliError> { + if let Some(matches) = matches.subcommand_matches("define") { + return Ok(Some(ApiCommand::Entity(EntityCommand::Create { + external_id: name_from::(matches, "external_id", "id")?, + namespace: namespace_from(matches)?, + attributes: attributes_from(matches, &self.entity.external_id, &self.attributes)?, + }))) + } + + if let Some(matches) = matches.subcommand_matches("derive") { + return Ok(Some(ApiCommand::Entity(EntityCommand::Derive { + namespace: namespace_from(matches)?, + id: id_from(matches, "generated_entity_id")?, + derivation: matches + .get_one::("subtype") + .map(|v| match v.as_str() { + "revision" => DerivationType::Revision, + "quotation" => DerivationType::Quotation, + "primary-source" => DerivationType::PrimarySource, + _ => unreachable!(), // Guaranteed by PossibleValuesParser + }) + .unwrap_or(DerivationType::None), + activity: id_from_option(matches, "activity_id")?, + used_entity: id_from(matches, "used_entity_id")?, + }))) + } + + Ok(None) + } } pub struct CliModel { - pub domain: ChronicleDomainDef, - pub agents: Vec, - pub entities: Vec, - pub activities: Vec, + pub domain: ChronicleDomainDef, + pub agents: Vec, + pub entities: Vec, + pub activities: Vec, } pub const LONG_VERSION: &str = const_format::formatcp!( - "{}:{} ({})", - env!("CARGO_PKG_VERSION"), - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../.VERSION")), - if cfg!(feature = "inmem") { - "in memory" - } else { - "sawtooth" - } + "{}:{} ({})", + env!("CARGO_PKG_VERSION"), + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../.VERSION")), + if cfg!(feature = "inmem") { "in memory" } else { "sawtooth" } ); impl From for CliModel { - fn from(val: ChronicleDomainDef) -> Self { - info!(chronicle_version = LONG_VERSION); - CliModel { - agents: val.agents.iter().map(AgentCliModel::new).collect(), - entities: val.entities.iter().map(EntityCliModel::new).collect(), - activities: val.activities.iter().map(ActivityCliModel::new).collect(), - domain: val, - } - } + fn from(val: ChronicleDomainDef) -> Self { + info!(chronicle_version = LONG_VERSION); + CliModel { + agents: val.agents.iter().map(AgentCliModel::new).collect(), + entities: val.entities.iter().map(EntityCliModel::new).collect(), + activities: val.activities.iter().map(ActivityCliModel::new).collect(), + domain: val, + } + } } impl SubCommand for CliModel { - fn as_cmd(&self) -> Command { - let mut app = Command::new("chronicle") + fn as_cmd(&self) -> Command { + let mut app = Command::new("chronicle") .version(LONG_VERSION) .author("Blockchain Technology Partners") .about("Write and query provenance data to distributed ledgers") @@ -1008,47 +972,47 @@ impl SubCommand for CliModel { ) ); - for agent in self.agents.iter() { - app = app.subcommand(agent.as_cmd()); - } - for activity in self.activities.iter() { - app = app.subcommand(activity.as_cmd()); - } - for entity in self.entities.iter() { - app = app.subcommand(entity.as_cmd()); - } - - #[cfg(not(feature = "inmem"))] - { - app = app.arg( - Arg::new("batcher-key-from-path") - .long("batcher-key-from-path") - .takes_value(true) - .value_hint(ValueHint::DirPath) - .help("Path to a directory containing the key for signing batches") - .conflicts_with("batcher-key-from-vault") - .conflicts_with("batcher-key-generated"), - ); - - app = app.arg( - Arg::new("batcher-key-from-vault") - .long("batcher-key-from-vault") - .takes_value(false) - .help("Use Hashicorp Vault to store the batcher key") - .conflicts_with("batcher-key-from-path") - .conflicts_with("batcher-key-generated"), - ); - - app = app.arg( - Arg::new("batcher-key-generated") - .long("batcher-key-generated") - .takes_value(false) - .help("Generate the batcher key in memory") - .conflicts_with("batcher-key-from-path") - .conflicts_with("batcher-key-from-vault"), - ); - - app = app.arg( + for agent in self.agents.iter() { + app = app.subcommand(agent.as_cmd()); + } + for activity in self.activities.iter() { + app = app.subcommand(activity.as_cmd()); + } + for entity in self.entities.iter() { + app = app.subcommand(entity.as_cmd()); + } + + #[cfg(not(feature = "inmem"))] + { + app = app.arg( + Arg::new("batcher-key-from-path") + .long("batcher-key-from-path") + .takes_value(true) + .value_hint(ValueHint::DirPath) + .help("Path to a directory containing the key for signing batches") + .conflicts_with("batcher-key-from-vault") + .conflicts_with("batcher-key-generated"), + ); + + app = app.arg( + Arg::new("batcher-key-from-vault") + .long("batcher-key-from-vault") + .takes_value(false) + .help("Use Hashicorp Vault to store the batcher key") + .conflicts_with("batcher-key-from-path") + .conflicts_with("batcher-key-generated"), + ); + + app = app.arg( + Arg::new("batcher-key-generated") + .long("batcher-key-generated") + .takes_value(false) + .help("Generate the batcher key in memory") + .conflicts_with("batcher-key-from-path") + .conflicts_with("batcher-key-from-vault"), + ); + + app = app.arg( Arg::new("chronicle-key-from-path") .long("chronicle-key-from-path") .takes_value(true) @@ -1058,107 +1022,103 @@ impl SubCommand for CliModel { .conflicts_with("chronicle-key-generated"), ); - app = app.arg( - Arg::new("chronicle-key-from-vault") - .long("chronicle-key-from-vault") - .takes_value(false) - .help("Use Hashicorp Vault to store the Chronicle key") - .conflicts_with("chronicle-key-from-path") - .conflicts_with("chronicle-key-generated"), - ); - - app = app.arg( - Arg::new("chronicle-key-generated") - .long("chronicle-key-generated") - .takes_value(false) - .help("Generate the Chronicle key in memory") - .conflicts_with("chronicle-key-from-path") - .conflicts_with("chronicle-key-from-vault"), - ); - - app = app.arg( - Arg::new("vault-address") - .long("vault-address") - .takes_value(true) - .value_hint(ValueHint::Url) - .help("URL for connecting to Hashicorp Vault") - .env("VAULT_ADDRESS"), - ); - - app = app.arg( - Arg::new("vault-token") - .long("vault-token") - .takes_value(true) - .help("Token for connecting to Hashicorp Vault") - .env("VAULT_TOKEN"), - ); - - app = app.arg( - Arg::new("vault-mount-path") - .long("vault-mount-path") - .takes_value(true) - .value_hint(ValueHint::DirPath) - .help("Mount path for vault secrets") - .env("VAULT_MOUNT_PATH"), - ); - - app.arg( - // default is provided by cargo.toml - Arg::new("sawtooth") - .long("sawtooth") - .value_name("sawtooth") - .value_hint(ValueHint::Url) - .help("Sets sawtooth validator address") - .takes_value(true), - ) - .arg( - Arg::new("embedded-opa-policy") - .long("embedded-opa-policy") - .takes_value(false) - .help( - "Operate without an external OPA policy, using an embedded default policy", - ), - ) - } - #[cfg(feature = "inmem")] - { - app - } - } - - /// Iterate our possible subcommands via model and short circuit with the first one that matches - fn matches(&self, matches: &ArgMatches) -> Result, CliError> { - for (agent, matches) in self.agents.iter().filter_map(|agent| { - matches - .subcommand_matches(&agent.external_id) - .map(|matches| (agent, matches)) - }) { - if let Some(cmd) = agent.matches(matches)? { - return Ok(Some(cmd)); - } - } - for (entity, matches) in self.entities.iter().filter_map(|entity| { - matches - .subcommand_matches(&entity.external_id) - .map(|matches| (entity, matches)) - }) { - if let Some(cmd) = entity.matches(matches)? { - return Ok(Some(cmd)); - } - } - for (activity, matches) in self.activities.iter().filter_map(|activity| { - matches - .subcommand_matches(&activity.external_id) - .map(|matches| (activity, matches)) - }) { - if let Some(cmd) = activity.matches(matches)? { - return Ok(Some(cmd)); - } - } - Ok(None) - } + app = app.arg( + Arg::new("chronicle-key-from-vault") + .long("chronicle-key-from-vault") + .takes_value(false) + .help("Use Hashicorp Vault to store the Chronicle key") + .conflicts_with("chronicle-key-from-path") + .conflicts_with("chronicle-key-generated"), + ); + + app = app.arg( + Arg::new("chronicle-key-generated") + .long("chronicle-key-generated") + .takes_value(false) + .help("Generate the Chronicle key in memory") + .conflicts_with("chronicle-key-from-path") + .conflicts_with("chronicle-key-from-vault"), + ); + + app = app.arg( + Arg::new("vault-address") + .long("vault-address") + .takes_value(true) + .value_hint(ValueHint::Url) + .help("URL for connecting to Hashicorp Vault") + .env("VAULT_ADDRESS"), + ); + + app = app.arg( + Arg::new("vault-token") + .long("vault-token") + .takes_value(true) + .help("Token for connecting to Hashicorp Vault") + .env("VAULT_TOKEN"), + ); + + app = app.arg( + Arg::new("vault-mount-path") + .long("vault-mount-path") + .takes_value(true) + .value_hint(ValueHint::DirPath) + .help("Mount path for vault secrets") + .env("VAULT_MOUNT_PATH"), + ); + + app.arg( + // default is provided by cargo.toml + Arg::new("sawtooth") + .long("sawtooth") + .value_name("sawtooth") + .value_hint(ValueHint::Url) + .help("Sets sawtooth validator address") + .takes_value(true), + ) + .arg( + Arg::new("embedded-opa-policy") + .long("embedded-opa-policy") + .takes_value(false) + .help( + "Operate without an external OPA policy, using an embedded default policy", + ), + ) + } + #[cfg(feature = "inmem")] + { + app + } + } + + /// Iterate our possible subcommands via model and short circuit with the first one that matches + fn matches(&self, matches: &ArgMatches) -> Result, CliError> { + for (agent, matches) in self.agents.iter().filter_map(|agent| { + matches.subcommand_matches(&agent.external_id).map(|matches| (agent, matches)) + }) { + if let Some(cmd) = agent.matches(matches)? { + return Ok(Some(cmd)) + } + } + for (entity, matches) in self.entities.iter().filter_map(|entity| { + matches.subcommand_matches(&entity.external_id).map(|matches| (entity, matches)) + }) { + if let Some(cmd) = entity.matches(matches)? { + return Ok(Some(cmd)) + } + } + for (activity, matches) in self.activities.iter().filter_map(|activity| { + matches + .subcommand_matches(&activity.external_id) + .map(|matches| (activity, matches)) + }) { + if let Some(cmd) = activity.matches(matches)? { + return Ok(Some(cmd)) + } + } + Ok(None) + } } pub fn cli(domain: ChronicleDomainDef) -> CliModel { - CliModel::from(domain) + CliModel::from(domain) } diff --git a/crates/chronicle/src/bootstrap/mod.rs b/crates/chronicle/src/bootstrap/mod.rs index 2c7dd3005..c0b2c5fde 100644 --- a/crates/chronicle/src/bootstrap/mod.rs +++ b/crates/chronicle/src/bootstrap/mod.rs @@ -4,34 +4,34 @@ mod opa; #[cfg(feature = "inmem")] use api::inmem::EmbeddedChronicleTp; use api::{ - chronicle_graphql::{ChronicleApiServer, ChronicleGraphQl, JwksUri, SecurityConf, UserInfoUri}, - Api, ApiDispatch, ApiError, StoreError, UuidGen, + chronicle_graphql::{ChronicleApiServer, ChronicleGraphQl, JwksUri, SecurityConf, UserInfoUri}, + Api, ApiDispatch, ApiError, StoreError, UuidGen, }; use async_graphql::{async_trait, ObjectType}; #[cfg(not(feature = "inmem"))] use chronicle_protocol::{ - address::{FAMILY, VERSION}, - ChronicleLedger, + address::{FAMILY, VERSION}, + ChronicleLedger, }; use chronicle_signing::{ - chronicle_secret_names, ChronicleSecretsOptions, ChronicleSigning, BATCHER_NAMESPACE, - CHRONICLE_NAMESPACE, + chronicle_secret_names, ChronicleSecretsOptions, ChronicleSigning, BATCHER_NAMESPACE, + CHRONICLE_NAMESPACE, }; use clap::{ArgMatches, Command}; use clap_complete::{generate, Generator, Shell}; pub use cli::*; use common::{ - commands::ApiResponse, - database::{get_connection_with_retry, DatabaseConnector}, - identity::AuthId, - import::{load_bytes_from_stdin, load_bytes_from_url}, - k256::{ - pkcs8::{EncodePrivateKey, LineEnding}, - SecretKey, - }, - ledger::SubmissionStage, - opa::ExecutorContext, - prov::{operations::ChronicleOperation, to_json_ld::ToJson, NamespaceId}, + commands::ApiResponse, + database::{get_connection_with_retry, DatabaseConnector}, + identity::AuthId, + import::{load_bytes_from_stdin, load_bytes_from_url}, + k256::{ + pkcs8::{EncodePrivateKey, LineEnding}, + SecretKey, + }, + ledger::SubmissionStage, + opa::ExecutorContext, + prov::{operations::ChronicleOperation, to_json_ld::ToJson, NamespaceId}, }; use rand::rngs::StdRng; use rand_core::SeedableRng; @@ -40,20 +40,20 @@ use tracing::{debug, error, info, instrument, warn}; use user_error::UFE; use diesel::{ - r2d2::{ConnectionManager, Pool}, - PgConnection, + r2d2::{ConnectionManager, Pool}, + PgConnection, }; use chronicle_telemetry::{self, ConsoleLogging}; use url::Url; use std::{ - collections::{BTreeSet, HashMap}, - fs::File, - io::{self, Write}, - net::{SocketAddr, ToSocketAddrs}, - path::PathBuf, - str::FromStr, + collections::{BTreeSet, HashMap}, + fs::File, + io::{self, Write}, + net::{SocketAddr, ToSocketAddrs}, + path::PathBuf, + str::FromStr, }; use crate::codegen::ChronicleDomainDef; @@ -62,47 +62,45 @@ use self::opa::opa_executor_from_embedded_policy; #[cfg(not(feature = "inmem"))] fn sawtooth_address(options: &ArgMatches) -> Result, CliError> { - Ok(options - .value_of("sawtooth") - .map(str::to_string) - .ok_or(CliError::MissingArgument { - arg: "sawtooth".to_owned(), - }) - .and_then(|s| Url::parse(&s).map_err(CliError::from)) - .map(|u| u.socket_addrs(|| Some(4004))) - .map_err(CliError::from)??) + Ok(options + .value_of("sawtooth") + .map(str::to_string) + .ok_or(CliError::MissingArgument { arg: "sawtooth".to_owned() }) + .and_then(|s| Url::parse(&s).map_err(CliError::from)) + .map(|u| u.socket_addrs(|| Some(4004))) + .map_err(CliError::from)??) } #[allow(dead_code)] #[cfg(not(feature = "inmem"))] fn ledger(options: &ArgMatches) -> Result { - use async_stl_client::zmq_client::{ - HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel, - }; - - Ok(ChronicleLedger::new( - ZmqRequestResponseSawtoothChannel::new( - "inmem", - &sawtooth_address(options)?, - HighestBlockValidatorSelector, - )? - .retrying(), - FAMILY, - VERSION, - )) + use async_stl_client::zmq_client::{ + HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel, + }; + + Ok(ChronicleLedger::new( + ZmqRequestResponseSawtoothChannel::new( + "inmem", + &sawtooth_address(options)?, + HighestBlockValidatorSelector, + )? + .retrying(), + FAMILY, + VERSION, + )) } #[allow(dead_code)] fn in_mem_ledger( - _options: &ArgMatches, + _options: &ArgMatches, ) -> Result { - Ok(crate::api::inmem::EmbeddedChronicleTp::new()?) + Ok(crate::api::inmem::EmbeddedChronicleTp::new()?) } #[cfg(feature = "inmem")] #[allow(dead_code)] fn ledger() -> Result { - Ok(EmbeddedChronicleTp::new()?) + Ok(EmbeddedChronicleTp::new()?) } #[derive(Debug, Clone)] @@ -113,241 +111,217 @@ impl UuidGen for UniqueUuid {} type ConnectionPool = Pool>; struct RemoteDatabaseConnector { - db_uri: String, + db_uri: String, } #[async_trait::async_trait] impl DatabaseConnector<(), StoreError> for RemoteDatabaseConnector { - async fn try_connect(&self) -> Result<((), Pool>), StoreError> { - use diesel::Connection; - PgConnection::establish(&self.db_uri)?; - Ok(( - (), - Pool::builder().build(ConnectionManager::::new(&self.db_uri))?, - )) - } - - fn should_retry(&self, error: &StoreError) -> bool { - matches!( - error, - StoreError::DbConnection(diesel::ConnectionError::BadConnection(_)) - ) - } + async fn try_connect(&self) -> Result<((), Pool>), StoreError> { + use diesel::Connection; + PgConnection::establish(&self.db_uri)?; + Ok(((), Pool::builder().build(ConnectionManager::::new(&self.db_uri))?)) + } + + fn should_retry(&self, error: &StoreError) -> bool { + matches!(error, StoreError::DbConnection(diesel::ConnectionError::BadConnection(_))) + } } #[instrument(skip(db_uri))] //Do not log db_uri, as it can contain passwords async fn pool_remote(db_uri: impl ToString) -> Result { - let (_, pool) = get_connection_with_retry(RemoteDatabaseConnector { - db_uri: db_uri.to_string(), - }) - .await?; - Ok(pool) + let (_, pool) = + get_connection_with_retry(RemoteDatabaseConnector { db_uri: db_uri.to_string() }).await?; + Ok(pool) } pub async fn api_server( - api: &ApiDispatch, - pool: &ConnectionPool, - gql: ChronicleGraphQl, - interface: Option>, - security_conf: SecurityConf, - serve_graphql: bool, - serve_data: bool, + api: &ApiDispatch, + pool: &ConnectionPool, + gql: ChronicleGraphQl, + interface: Option>, + security_conf: SecurityConf, + serve_graphql: bool, + serve_data: bool, ) -> Result<(), ApiError> where - Query: ObjectType + Copy, - Mutation: ObjectType + Copy, + Query: ObjectType + Copy, + Mutation: ObjectType + Copy, { - if let Some(addresses) = interface { - gql.serve_api( - pool.clone(), - api.clone(), - addresses, - security_conf, - serve_graphql, - serve_data, - ) - .await? - } - - Ok(()) + if let Some(addresses) = interface { + gql.serve_api( + pool.clone(), + api.clone(), + addresses, + security_conf, + serve_graphql, + serve_data, + ) + .await? + } + + Ok(()) } #[allow(dead_code)] fn namespace_bindings(options: &ArgMatches) -> Vec { - options - .values_of("namespace-bindings") - .map(|values| { - values - .map(|value| { - let (id, uuid) = value.split_once(':').unwrap(); - - let uuid = uuid::Uuid::parse_str(uuid).unwrap(); - NamespaceId::from_external_id(id, uuid) - }) - .collect() - }) - .unwrap_or_default() + options + .values_of("namespace-bindings") + .map(|values| { + values + .map(|value| { + let (id, uuid) = value.split_once(':').unwrap(); + + let uuid = uuid::Uuid::parse_str(uuid).unwrap(); + NamespaceId::from_external_id(id, uuid) + }) + .collect() + }) + .unwrap_or_default() } fn vault_secrets_options(options: &ArgMatches) -> Result { - let vault_url = options - .value_of("vault-url") - .ok_or_else(|| CliError::missing_argument("vault-url"))?; - let token = options - .value_of("vault-token") - .ok_or_else(|| CliError::missing_argument("vault-token"))?; - let mount_path = options - .value_of("vault-mount-path") - .ok_or_else(|| CliError::missing_argument("vault-mount-path"))?; - Ok(ChronicleSecretsOptions::stored_in_vault( - &Url::parse(vault_url)?, - token, - mount_path, - )) + let vault_url = options + .value_of("vault-url") + .ok_or_else(|| CliError::missing_argument("vault-url"))?; + let token = options + .value_of("vault-token") + .ok_or_else(|| CliError::missing_argument("vault-token"))?; + let mount_path = options + .value_of("vault-mount-path") + .ok_or_else(|| CliError::missing_argument("vault-mount-path"))?; + Ok(ChronicleSecretsOptions::stored_in_vault(&Url::parse(vault_url)?, token, mount_path)) } async fn chronicle_signing(options: &ArgMatches) -> Result { - // Determine batcher configuration - let batcher_options = match ( - options.get_one::("batcher-key-from-path"), - options.get_flag("batcher-key-from-vault"), - options.get_flag("batcher-key-generated"), - ) { - (Some(path), _, _) => ChronicleSecretsOptions::stored_at_path(path), - (_, true, _) => vault_secrets_options(options)?, - (_, _, true) => ChronicleSecretsOptions::generate_in_memory(), - _ => unreachable!("CLI should always set batcher key"), - }; - - let chronicle_options = match ( - options.get_one::("chronicle-key-from-path"), - options.get_flag("chronicle-key-from-vault"), - options.get_flag("chronicle-key-generated"), - ) { - (Some(path), _, _) => ChronicleSecretsOptions::stored_at_path(path), - (_, true, _) => vault_secrets_options(options)?, - (_, _, true) => ChronicleSecretsOptions::generate_in_memory(), - _ => unreachable!("CLI should always set chronicle key"), - }; - - Ok(ChronicleSigning::new( - chronicle_secret_names(), - vec![ - (CHRONICLE_NAMESPACE.to_string(), chronicle_options), - (BATCHER_NAMESPACE.to_string(), batcher_options), - ], - ) - .await?) + // Determine batcher configuration + let batcher_options = match ( + options.get_one::("batcher-key-from-path"), + options.get_flag("batcher-key-from-vault"), + options.get_flag("batcher-key-generated"), + ) { + (Some(path), _, _) => ChronicleSecretsOptions::stored_at_path(path), + (_, true, _) => vault_secrets_options(options)?, + (_, _, true) => ChronicleSecretsOptions::generate_in_memory(), + _ => unreachable!("CLI should always set batcher key"), + }; + + let chronicle_options = match ( + options.get_one::("chronicle-key-from-path"), + options.get_flag("chronicle-key-from-vault"), + options.get_flag("chronicle-key-generated"), + ) { + (Some(path), _, _) => ChronicleSecretsOptions::stored_at_path(path), + (_, true, _) => vault_secrets_options(options)?, + (_, _, true) => ChronicleSecretsOptions::generate_in_memory(), + _ => unreachable!("CLI should always set chronicle key"), + }; + + Ok(ChronicleSigning::new( + chronicle_secret_names(), + vec![ + (CHRONICLE_NAMESPACE.to_string(), chronicle_options), + (BATCHER_NAMESPACE.to_string(), batcher_options), + ], + ) + .await?) } #[cfg(not(feature = "inmem"))] pub async fn api( - pool: &ConnectionPool, - options: &ArgMatches, - policy_name: Option, - liveness_check_interval: Option, + pool: &ConnectionPool, + options: &ArgMatches, + policy_name: Option, + liveness_check_interval: Option, ) -> Result { - let ledger = ledger(options)?; - - Ok(Api::new( - pool.clone(), - ledger, - UniqueUuid, - chronicle_signing(options).await?, - namespace_bindings(options), - policy_name, - liveness_check_interval, - ) - .await?) + let ledger = ledger(options)?; + + Ok(Api::new( + pool.clone(), + ledger, + UniqueUuid, + chronicle_signing(options).await?, + namespace_bindings(options), + policy_name, + liveness_check_interval, + ) + .await?) } #[cfg(feature = "inmem")] pub async fn api( - pool: &ConnectionPool, - options: &ArgMatches, - remote_opa: Option, - liveness_check_interval: Option, + pool: &ConnectionPool, + options: &ArgMatches, + remote_opa: Option, + liveness_check_interval: Option, ) -> Result { - let embedded_tp = in_mem_ledger(options)?; - - Ok(Api::new( - pool.clone(), - embedded_tp.ledger, - UniqueUuid, - chronicle_signing(options).await?, - vec![], - remote_opa, - liveness_check_interval, - ) - .await?) + let embedded_tp = in_mem_ledger(options)?; + + Ok(Api::new( + pool.clone(), + embedded_tp.ledger, + UniqueUuid, + chronicle_signing(options).await?, + vec![], + remote_opa, + liveness_check_interval, + ) + .await?) } fn construct_db_uri(matches: &ArgMatches) -> String { - fn encode(string: &str) -> String { - use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; - utf8_percent_encode(string, NON_ALPHANUMERIC).to_string() - } - - let password = match std::env::var("PGPASSWORD") { - Ok(password) => { - debug!("PGPASSWORD is set, using for DB connection"); - format!(":{}", encode(password.as_str())) - } - Err(_) => { - debug!("PGPASSWORD is not set, omitting for DB connection"); - String::new() - } - }; - - format!( - "postgresql://{}{}@{}:{}/{}", - encode( - matches - .value_of("database-username") - .expect("CLI should always set database user") - ), - password, - encode( - matches - .value_of("database-host") - .expect("CLI should always set database host") - ), - encode( - matches - .value_of("database-port") - .expect("CLI should always set database port") - ), - encode( - matches - .value_of("database-name") - .expect("CLI should always set database name") - ) - ) + fn encode(string: &str) -> String { + use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; + utf8_percent_encode(string, NON_ALPHANUMERIC).to_string() + } + + let password = match std::env::var("PGPASSWORD") { + Ok(password) => { + debug!("PGPASSWORD is set, using for DB connection"); + format!(":{}", encode(password.as_str())) + }, + Err(_) => { + debug!("PGPASSWORD is not set, omitting for DB connection"); + String::new() + }, + }; + + format!( + "postgresql://{}{}@{}:{}/{}", + encode( + matches + .value_of("database-username") + .expect("CLI should always set database user") + ), + password, + encode(matches.value_of("database-host").expect("CLI should always set database host")), + encode(matches.value_of("database-port").expect("CLI should always set database port")), + encode(matches.value_of("database-name").expect("CLI should always set database name")) + ) } #[derive(Debug, Clone)] pub enum ConfiguredOpa { - Embedded(ExecutorContext), - Remote(ExecutorContext, chronicle_protocol::settings::OpaSettings), - Url(ExecutorContext), + Embedded(ExecutorContext), + Remote(ExecutorContext, chronicle_protocol::settings::OpaSettings), + Url(ExecutorContext), } impl ConfiguredOpa { - pub fn context(&self) -> &ExecutorContext { - match self { - ConfiguredOpa::Embedded(context) => context, - ConfiguredOpa::Remote(context, _) => context, - ConfiguredOpa::Url(context) => context, - } - } - - pub fn remote_settings(&self) -> Option { - match self { - ConfiguredOpa::Embedded(_) => None, - ConfiguredOpa::Remote(_, settings) => Some(settings.policy_name.clone()), - ConfiguredOpa::Url(_) => None, - } - } + pub fn context(&self) -> &ExecutorContext { + match self { + ConfiguredOpa::Embedded(context) => context, + ConfiguredOpa::Remote(context, _) => context, + ConfiguredOpa::Url(context) => context, + } + } + + pub fn remote_settings(&self) -> Option { + match self { + ConfiguredOpa::Embedded(_) => None, + ConfiguredOpa::Remote(_, settings) => Some(settings.policy_name.clone()), + ConfiguredOpa::Url(_) => None, + } + } } /// If embedded-opa-policy is set, we will use the embedded policy, otherwise we @@ -356,238 +330,227 @@ impl ConfiguredOpa { #[cfg(feature = "inmem")] #[allow(unused_variables)] async fn configure_opa(options: &ArgMatches) -> Result { - let (default_policy_name, entrypoint) = - ("allow_transactions", "allow_transactions.allowed_users"); - let opa = opa_executor_from_embedded_policy(default_policy_name, entrypoint).await?; - Ok(ConfiguredOpa::Embedded(opa)) + let (default_policy_name, entrypoint) = + ("allow_transactions", "allow_transactions.allowed_users"); + let opa = opa_executor_from_embedded_policy(default_policy_name, entrypoint).await?; + Ok(ConfiguredOpa::Embedded(opa)) } #[cfg(not(feature = "inmem"))] #[instrument(skip(options))] async fn configure_opa(options: &ArgMatches) -> Result { - if options.is_present("embedded-opa-policy") { - let (default_policy_name, entrypoint) = - ("allow_transactions", "allow_transactions.allowed_users"); - let opa = opa_executor_from_embedded_policy(default_policy_name, entrypoint).await?; - tracing::warn!( - "Chronicle operating in an insecure mode with an embedded default OPA policy" - ); - Ok(ConfiguredOpa::Embedded(opa)) - } else if let Some(url) = options.value_of("opa-bundle-address") { - let (policy_name, entrypoint) = ( - options.value_of("opa-policy-name").unwrap(), - options.value_of("opa-policy-entrypoint").unwrap(), - ); - let opa = self::opa::opa_executor_from_url(url, policy_name, entrypoint).await?; - tracing::info!("Chronicle operating with OPA policy from URL"); - - Ok(ConfiguredOpa::Url(opa)) - } else { - let (opa, settings) = - self::opa::opa_executor_from_sawtooth_settings(&sawtooth_address(options)?).await?; - tracing::info!(use_on_chain_opa= ?settings, "Chronicle operating in secure mode with on chain OPA policy"); - - Ok(ConfiguredOpa::Remote(opa, settings)) - } + if options.is_present("embedded-opa-policy") { + let (default_policy_name, entrypoint) = + ("allow_transactions", "allow_transactions.allowed_users"); + let opa = opa_executor_from_embedded_policy(default_policy_name, entrypoint).await?; + tracing::warn!( + "Chronicle operating in an insecure mode with an embedded default OPA policy" + ); + Ok(ConfiguredOpa::Embedded(opa)) + } else if let Some(url) = options.value_of("opa-bundle-address") { + let (policy_name, entrypoint) = ( + options.value_of("opa-policy-name").unwrap(), + options.value_of("opa-policy-entrypoint").unwrap(), + ); + let opa = self::opa::opa_executor_from_url(url, policy_name, entrypoint).await?; + tracing::info!("Chronicle operating with OPA policy from URL"); + + Ok(ConfiguredOpa::Url(opa)) + } else { + let (opa, settings) = + self::opa::opa_executor_from_sawtooth_settings(&sawtooth_address(options)?).await?; + tracing::info!(use_on_chain_opa= ?settings, "Chronicle operating in secure mode with on chain OPA policy"); + + Ok(ConfiguredOpa::Remote(opa, settings)) + } } -/// If `--liveness-check` is set, we use either the interval in seconds provided or the default of 1800. -/// Otherwise, we use `None` to disable the depth charge. +/// If `--liveness-check` is set, we use either the interval in seconds provided or the default of +/// 1800. Otherwise, we use `None` to disable the depth charge. fn configure_depth_charge(matches: &ArgMatches) -> Option { - if let Some(serve_api_matches) = matches.subcommand_matches("serve-api") { - if let Some(interval) = serve_api_matches.value_of("liveness-check") { - let parsed_interval = interval.parse::().unwrap_or_else(|e| { - warn!("Failed to parse '--liveness-check' value: {e}"); - 1800 - }); - - if parsed_interval == 1800 { - debug!("Using default liveness health check interval value: 1800"); - } else { - debug!("Using custom liveness health check interval value: {parsed_interval}"); - } - return Some(parsed_interval); - } - } - debug!("Liveness health check disabled"); - None + if let Some(serve_api_matches) = matches.subcommand_matches("serve-api") { + if let Some(interval) = serve_api_matches.value_of("liveness-check") { + let parsed_interval = interval.parse::().unwrap_or_else(|e| { + warn!("Failed to parse '--liveness-check' value: {e}"); + 1800 + }); + + if parsed_interval == 1800 { + debug!("Using default liveness health check interval value: 1800"); + } else { + debug!("Using custom liveness health check interval value: {parsed_interval}"); + } + return Some(parsed_interval) + } + } + debug!("Liveness health check disabled"); + None } #[instrument(skip(gql, cli))] async fn execute_subcommand( - gql: ChronicleGraphQl, - cli: CliModel, + gql: ChronicleGraphQl, + cli: CliModel, ) -> Result<(ApiResponse, ApiDispatch), CliError> where - Query: ObjectType + Copy, - Mutation: ObjectType + Copy, + Query: ObjectType + Copy, + Mutation: ObjectType + Copy, { - dotenvy::dotenv().ok(); - - let matches = cli.as_cmd().get_matches(); - - let pool = pool_remote(&construct_db_uri(&matches)).await?; - - let opa = configure_opa(&matches).await?; - - let liveness_check_interval = configure_depth_charge(&matches); - - let api = api( - &pool, - &matches, - opa.remote_settings(), - liveness_check_interval, - ) - .await?; - let ret_api = api.clone(); - - if let Some(matches) = matches.subcommand_matches("serve-api") { - let interface = match matches.get_many::("interface") { - Some(interface_args) => { - let mut addrs = Vec::new(); - for interface_arg in interface_args { - addrs.extend(interface_arg.to_socket_addrs()?); - } - Some(addrs) - } - None => None, - }; - - let jwks_uri = if let Some(uri) = matches.value_of("jwks-address") { - Some(JwksUri::new(Url::from_str(uri)?)) - } else { - None - }; - - let userinfo_uri = if let Some(uri) = matches.value_of("userinfo-address") { - Some(UserInfoUri::new(Url::from_str(uri)?)) - } else { - None - }; - - let allow_anonymous = !matches.is_present("require-auth"); - - let id_claims = matches.get_many::("id-claims").map(|id_claims| { - let mut id_keys = BTreeSet::new(); - for id_claim in id_claims { - id_keys.extend(id_claim.split_whitespace().map(|s| s.to_string())); - } - id_keys - }); - - let mut jwt_must_claim: HashMap = HashMap::new(); - for (name, value) in std::env::vars() { - if let Some(name) = name.strip_prefix("JWT_MUST_CLAIM_") { - jwt_must_claim.insert(name.to_lowercase(), value); - } - } - if let Some(mut claims) = matches.get_many::("jwt-must-claim") { - while let (Some(name), Some(value)) = (claims.next(), claims.next()) { - jwt_must_claim.insert(name.clone(), value.clone()); - } - } - - let endpoints: Vec = matches - .get_many("offer-endpoints") - .unwrap() - .map(String::clone) - .collect(); - - api_server( - &api, - &pool, - gql, - interface, - SecurityConf::new( - jwks_uri, - userinfo_uri, - id_claims, - jwt_must_claim, - allow_anonymous, - opa.context().clone(), - ), - endpoints.contains(&"graphql".to_string()), - endpoints.contains(&"data".to_string()), - ) - .await?; - - Ok((ApiResponse::Unit, ret_api)) - } else if let Some(matches) = matches.subcommand_matches("import") { - let namespace = get_namespace(matches); - - let data = if let Some(url) = matches.value_of("url") { - let data = load_bytes_from_url(url).await?; - info!("Loaded import data from {:?}", url); - data - } else { - if std::io::stdin().is_terminal() { - eprintln!("Attempting to import data from standard input, press Ctrl-D to finish."); - } - info!("Attempting to read import data from stdin..."); - let data = load_bytes_from_stdin()?; - info!("Loaded {} bytes of import data from stdin", data.len()); - data - }; - - let data = std::str::from_utf8(&data)?; - - if data.trim().is_empty() { - eprintln!("Import data is empty, nothing to import"); - return Ok((ApiResponse::Unit, ret_api)); - } - - let json_array = serde_json::from_str::>(data)?; - - let mut operations = Vec::new(); - for value in json_array.into_iter() { - let op = ChronicleOperation::from_json(&value) - .await - .expect("Failed to parse imported JSON-LD to ChronicleOperation"); - // Only import operations for the specified namespace - if op.namespace() == &namespace { - operations.push(op); - } - } - - info!("Loading import data complete"); - - let identity = AuthId::chronicle(); - info!("Importing data as root to Chronicle namespace: {namespace}"); - - let response = api - .handle_import_command(identity, namespace, operations) - .await?; - - Ok((response, ret_api)) - } else if let Some(cmd) = cli.matches(&matches)? { - let identity = AuthId::chronicle(); - Ok((api.dispatch(cmd, identity).await?, ret_api)) - } else { - Ok((ApiResponse::Unit, ret_api)) - } + dotenvy::dotenv().ok(); + + let matches = cli.as_cmd().get_matches(); + + let pool = pool_remote(&construct_db_uri(&matches)).await?; + + let opa = configure_opa(&matches).await?; + + let liveness_check_interval = configure_depth_charge(&matches); + + let api = api(&pool, &matches, opa.remote_settings(), liveness_check_interval).await?; + let ret_api = api.clone(); + + if let Some(matches) = matches.subcommand_matches("serve-api") { + let interface = match matches.get_many::("interface") { + Some(interface_args) => { + let mut addrs = Vec::new(); + for interface_arg in interface_args { + addrs.extend(interface_arg.to_socket_addrs()?); + } + Some(addrs) + }, + None => None, + }; + + let jwks_uri = if let Some(uri) = matches.value_of("jwks-address") { + Some(JwksUri::new(Url::from_str(uri)?)) + } else { + None + }; + + let userinfo_uri = if let Some(uri) = matches.value_of("userinfo-address") { + Some(UserInfoUri::new(Url::from_str(uri)?)) + } else { + None + }; + + let allow_anonymous = !matches.is_present("require-auth"); + + let id_claims = matches.get_many::("id-claims").map(|id_claims| { + let mut id_keys = BTreeSet::new(); + for id_claim in id_claims { + id_keys.extend(id_claim.split_whitespace().map(|s| s.to_string())); + } + id_keys + }); + + let mut jwt_must_claim: HashMap = HashMap::new(); + for (name, value) in std::env::vars() { + if let Some(name) = name.strip_prefix("JWT_MUST_CLAIM_") { + jwt_must_claim.insert(name.to_lowercase(), value); + } + } + if let Some(mut claims) = matches.get_many::("jwt-must-claim") { + while let (Some(name), Some(value)) = (claims.next(), claims.next()) { + jwt_must_claim.insert(name.clone(), value.clone()); + } + } + + let endpoints: Vec = + matches.get_many("offer-endpoints").unwrap().map(String::clone).collect(); + + api_server( + &api, + &pool, + gql, + interface, + SecurityConf::new( + jwks_uri, + userinfo_uri, + id_claims, + jwt_must_claim, + allow_anonymous, + opa.context().clone(), + ), + endpoints.contains(&"graphql".to_string()), + endpoints.contains(&"data".to_string()), + ) + .await?; + + Ok((ApiResponse::Unit, ret_api)) + } else if let Some(matches) = matches.subcommand_matches("import") { + let namespace = get_namespace(matches); + + let data = if let Some(url) = matches.value_of("url") { + let data = load_bytes_from_url(url).await?; + info!("Loaded import data from {:?}", url); + data + } else { + if std::io::stdin().is_terminal() { + eprintln!("Attempting to import data from standard input, press Ctrl-D to finish."); + } + info!("Attempting to read import data from stdin..."); + let data = load_bytes_from_stdin()?; + info!("Loaded {} bytes of import data from stdin", data.len()); + data + }; + + let data = std::str::from_utf8(&data)?; + + if data.trim().is_empty() { + eprintln!("Import data is empty, nothing to import"); + return Ok((ApiResponse::Unit, ret_api)) + } + + let json_array = serde_json::from_str::>(data)?; + + let mut operations = Vec::new(); + for value in json_array.into_iter() { + let op = ChronicleOperation::from_json(&value) + .await + .expect("Failed to parse imported JSON-LD to ChronicleOperation"); + // Only import operations for the specified namespace + if op.namespace() == &namespace { + operations.push(op); + } + } + + info!("Loading import data complete"); + + let identity = AuthId::chronicle(); + info!("Importing data as root to Chronicle namespace: {namespace}"); + + let response = api.handle_import_command(identity, namespace, operations).await?; + + Ok((response, ret_api)) + } else if let Some(cmd) = cli.matches(&matches)? { + let identity = AuthId::chronicle(); + Ok((api.dispatch(cmd, identity).await?, ret_api)) + } else { + Ok((ApiResponse::Unit, ret_api)) + } } fn get_namespace(matches: &ArgMatches) -> NamespaceId { - let namespace_id = matches.value_of("namespace-id").unwrap(); - let namespace_uuid = matches.value_of("namespace-uuid").unwrap(); - let uuid = uuid::Uuid::try_parse(namespace_uuid) - .unwrap_or_else(|_| panic!("cannot parse namespace UUID: {}", namespace_uuid)); - NamespaceId::from_external_id(namespace_id, uuid) + let namespace_id = matches.value_of("namespace-id").unwrap(); + let namespace_uuid = matches.value_of("namespace-uuid").unwrap(); + let uuid = uuid::Uuid::try_parse(namespace_uuid) + .unwrap_or_else(|_| panic!("cannot parse namespace UUID: {}", namespace_uuid)); + NamespaceId::from_external_id(namespace_id, uuid) } async fn config_and_exec( - gql: ChronicleGraphQl, - model: CliModel, + gql: ChronicleGraphQl, + model: CliModel, ) -> Result<(), CliError> where - Query: ObjectType + Copy, - Mutation: ObjectType + Copy, + Query: ObjectType + Copy, + Mutation: ObjectType + Copy, { - use colored_json::prelude::*; + use colored_json::prelude::*; - let response = execute_subcommand(gql, model).await?; + let response = execute_subcommand(gql, model).await?; - match response { + match response { ( ApiResponse::Submission { subject, @@ -705,312 +668,291 @@ where "DepthChargeSubmitted is an unexpected API response for transaction: {tx_id}. Depth charge not implemented." ), }; - Ok(()) + Ok(()) } fn print_completions(gen: G, app: &mut Command) { - generate(gen, app, app.get_name().to_string(), &mut io::stdout()); + generate(gen, app, app.get_name().to_string(), &mut io::stdout()); } pub async fn bootstrap( - domain: ChronicleDomainDef, - gql: ChronicleGraphQl, + domain: ChronicleDomainDef, + gql: ChronicleGraphQl, ) where - Query: ObjectType + 'static + Copy, - Mutation: ObjectType + 'static + Copy, + Query: ObjectType + 'static + Copy, + Mutation: ObjectType + 'static + Copy, { - let matches = cli(domain.clone()).as_cmd().get_matches(); - - if let Some(generator) = matches.subcommand_matches("completions") { - let shell = generator - .get_one::("shell") - .unwrap() - .parse::() - .unwrap(); - print_completions(shell.to_owned(), &mut cli(domain.clone()).as_cmd()); - std::process::exit(0); - } - - if matches.subcommand_matches("export-schema").is_some() { - print!("{}", gql.exportable_schema()); - std::process::exit(0); - } - chronicle_telemetry::telemetry( - matches - .get_one::("instrument") - .map(|s| Url::parse(s).expect("cannot parse instrument as URI: {s}")), - if matches.contains_id("console-logging") { - match matches.get_one::("console-logging") { - Some(level) => match level.as_str() { - "pretty" => ConsoleLogging::Pretty, - "json" => ConsoleLogging::Json, - _ => ConsoleLogging::Off, - }, - _ => ConsoleLogging::Off, - } - } else if matches.subcommand_name() == Some("serve-api") { - ConsoleLogging::Pretty - } else { - ConsoleLogging::Off - }, - ); - - if matches.subcommand_matches("generate-key").is_some() { - let key = SecretKey::random(StdRng::from_entropy()); - let key = key.to_pkcs8_pem(LineEnding::CRLF).unwrap(); - - if let Some(path) = matches.get_one::("output") { - //TODO - clean up these unwraps, they always come up with fs / cli - let mut file = File::create(path).unwrap(); - file.write_all(key.as_bytes()).unwrap(); - } else { - print!("{}", *key); - } - - std::process::exit(0); - } - - config_and_exec(gql, domain.into()) - .await - .map_err(|e| { - error!(?e, "Api error"); - e.into_ufe().print(); - std::process::exit(1); - }) - .ok(); - - std::process::exit(0); + let matches = cli(domain.clone()).as_cmd().get_matches(); + + if let Some(generator) = matches.subcommand_matches("completions") { + let shell = generator.get_one::("shell").unwrap().parse::().unwrap(); + print_completions(shell.to_owned(), &mut cli(domain.clone()).as_cmd()); + std::process::exit(0); + } + + if matches.subcommand_matches("export-schema").is_some() { + print!("{}", gql.exportable_schema()); + std::process::exit(0); + } + chronicle_telemetry::telemetry( + matches + .get_one::("instrument") + .map(|s| Url::parse(s).expect("cannot parse instrument as URI: {s}")), + if matches.contains_id("console-logging") { + match matches.get_one::("console-logging") { + Some(level) => match level.as_str() { + "pretty" => ConsoleLogging::Pretty, + "json" => ConsoleLogging::Json, + _ => ConsoleLogging::Off, + }, + _ => ConsoleLogging::Off, + } + } else if matches.subcommand_name() == Some("serve-api") { + ConsoleLogging::Pretty + } else { + ConsoleLogging::Off + }, + ); + + if matches.subcommand_matches("generate-key").is_some() { + let key = SecretKey::random(StdRng::from_entropy()); + let key = key.to_pkcs8_pem(LineEnding::CRLF).unwrap(); + + if let Some(path) = matches.get_one::("output") { + //TODO - clean up these unwraps, they always come up with fs / cli + let mut file = File::create(path).unwrap(); + file.write_all(key.as_bytes()).unwrap(); + } else { + print!("{}", *key); + } + + std::process::exit(0); + } + + config_and_exec(gql, domain.into()) + .await + .map_err(|e| { + error!(?e, "Api error"); + e.into_ufe().print(); + std::process::exit(1); + }) + .ok(); + + std::process::exit(0); } /// We can only sensibly test subcommand parsing for the CLI's PROV actions, /// configuration + server execution would get a little tricky in the context of a unit test. #[cfg(test)] pub mod test { - use api::{inmem::EmbeddedChronicleTp, Api, ApiDispatch, ApiError, UuidGen}; - use async_stl_client::prost::Message; - use chronicle_signing::{ - chronicle_secret_names, ChronicleSecretsOptions, ChronicleSigning, BATCHER_NAMESPACE, - CHRONICLE_NAMESPACE, - }; - use common::{ - commands::{ApiCommand, ApiResponse}, - database::TemporaryDatabase, - identity::AuthId, - k256::sha2::{Digest, Sha256}, - ledger::SubmissionStage, - prov::{ - to_json_ld::ToJson, ActivityId, AgentId, ChronicleIri, ChronicleTransactionId, - EntityId, ProvModel, - }, - }; - use opa_tp_protocol::state::{policy_address, policy_meta_address, PolicyMeta}; - use uuid::Uuid; - - use super::{CliModel, SubCommand}; - use crate::codegen::ChronicleDomainDef; - - struct TestDispatch<'a> { - api: ApiDispatch, - _db: TemporaryDatabase<'a>, // share lifetime - _tp: EmbeddedChronicleTp, - } - - impl TestDispatch<'_> { - pub async fn dispatch( - &mut self, - command: ApiCommand, - identity: AuthId, - ) -> Result, ChronicleTransactionId)>, ApiError> { - // We can sort of get final on chain state here by using a map of subject to model - if let ApiResponse::Submission { .. } = self.api.dispatch(command, identity).await? { - loop { - let submission = self - .api - .notify_commit - .subscribe() - .recv() - .await - .expect("failed to receive response to submission"); - - if let SubmissionStage::Committed(commit, _) = submission { - break Ok(Some((commit.delta, commit.tx_id))); - } - if let SubmissionStage::NotCommitted((_, contradiction, _)) = submission { - panic!("Contradiction: {contradiction}"); - } - } - } else { - Ok(None) - } - } - } - - #[derive(Debug, Clone)] - struct SameUuid; - - impl UuidGen for SameUuid { - fn uuid() -> Uuid { - Uuid::parse_str("5a0ab5b8-eeb7-4812-9fe3-6dd69bd20cea").unwrap() - } - } - - async fn test_api<'a>() -> TestDispatch<'a> { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - - let secrets = ChronicleSigning::new( - chronicle_secret_names(), - vec![ - ( - CHRONICLE_NAMESPACE.to_string(), - ChronicleSecretsOptions::generate_in_memory(), - ), - ( - BATCHER_NAMESPACE.to_string(), - ChronicleSecretsOptions::generate_in_memory(), - ), - ], - ) - .await - .unwrap(); - - let buf = async_stl_client::messages::Setting { - entries: vec![async_stl_client::messages::setting::Entry { - key: "chronicle.opa.policy_name".to_string(), - value: "allow_transactions".to_string(), - }], - } - .encode_to_vec(); - let setting_id = ( - chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.policy_name"), - buf, - ); - let buf = async_stl_client::messages::Setting { - entries: vec![async_stl_client::messages::setting::Entry { - key: "chronicle.opa.entrypoint".to_string(), - value: "allow_transactions.allowed_users".to_string(), - }], - } - .encode_to_vec(); - - let setting_entrypoint = ( - chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.entrypoint"), - buf, - ); - - let d = env!("CARGO_MANIFEST_DIR").to_owned() + "/../../policies/bundle.tar.gz"; - let bin = std::fs::read(d).unwrap(); - - let meta = PolicyMeta { - id: "allow_transactions".to_string(), - hash: hex::encode(Sha256::digest(&bin)), - policy_address: policy_address("allow_transactions"), - }; - - let embedded_tp = EmbeddedChronicleTp::new_with_state( - vec![ - setting_id, - setting_entrypoint, - (policy_address("allow_transactions"), bin), - ( - policy_meta_address("allow_transactions"), - serde_json::to_vec(&meta).unwrap(), - ), - ] - .into_iter() - .collect(), - ) - .unwrap(); - - let database = TemporaryDatabase::default(); - let pool = database.connection_pool().unwrap(); - - let liveness_check_interval = None; - - let dispatch = Api::new( - pool, - embedded_tp.ledger.clone(), - SameUuid, - secrets, - vec![], - Some("allow_transactions".to_owned()), - liveness_check_interval, - ) - .await - .unwrap(); - - TestDispatch { - api: dispatch, - _db: database, - _tp: embedded_tp, - } - } - - fn get_api_cmd(command_line: &str) -> ApiCommand { - let cli = test_cli_model(); - let matches = cli - .as_cmd() - .get_matches_from(command_line.split_whitespace()); - cli.matches(&matches).unwrap().unwrap() - } - - async fn parse_and_execute(command_line: &str, cli: CliModel) -> Box { - let mut api = test_api().await; - - let matches = cli - .as_cmd() - .get_matches_from(command_line.split_whitespace()); - - let cmd = cli.matches(&matches).unwrap().unwrap(); - - let identity = AuthId::chronicle(); - - api.dispatch(cmd, identity).await.unwrap().unwrap().0 - } - - fn test_cli_model() -> CliModel { - CliModel::from( - ChronicleDomainDef::build("test") - .with_attribute_type("testString", None, crate::PrimitiveType::String) - .unwrap() - .with_attribute_type("testBool", None, crate::PrimitiveType::Bool) - .unwrap() - .with_attribute_type("testInt", None, crate::PrimitiveType::Int) - .unwrap() - .with_attribute_type("testJSON", None, crate::PrimitiveType::JSON) - .unwrap() - .with_activity("testActivity", None, |b| { - b.with_attribute("testString") - .unwrap() - .with_attribute("testBool") - .unwrap() - .with_attribute("testInt") - }) - .unwrap() - .with_agent("testAgent", None, |b| { - b.with_attribute("testString") - .unwrap() - .with_attribute("testBool") - .unwrap() - .with_attribute("testInt") - }) - .unwrap() - .with_entity("testEntity", None, |b| { - b.with_attribute("testString") - .unwrap() - .with_attribute("testBool") - .unwrap() - .with_attribute("testInt") - }) - .unwrap() - .build(), - ) - } - - #[tokio::test] - async fn agent_define() { - let command_line = r#"chronicle test-agent-agent define test_agent --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns "#; - - insta::assert_snapshot!( + use api::{inmem::EmbeddedChronicleTp, Api, ApiDispatch, ApiError, UuidGen}; + use async_stl_client::prost::Message; + use chronicle_signing::{ + chronicle_secret_names, ChronicleSecretsOptions, ChronicleSigning, BATCHER_NAMESPACE, + CHRONICLE_NAMESPACE, + }; + use common::{ + commands::{ApiCommand, ApiResponse}, + database::TemporaryDatabase, + identity::AuthId, + k256::sha2::{Digest, Sha256}, + ledger::SubmissionStage, + prov::{ + to_json_ld::ToJson, ActivityId, AgentId, ChronicleIri, ChronicleTransactionId, + EntityId, ProvModel, + }, + }; + use opa_tp_protocol::state::{policy_address, policy_meta_address, PolicyMeta}; + use uuid::Uuid; + + use super::{CliModel, SubCommand}; + use crate::codegen::ChronicleDomainDef; + + struct TestDispatch<'a> { + api: ApiDispatch, + _db: TemporaryDatabase<'a>, // share lifetime + _tp: EmbeddedChronicleTp, + } + + impl TestDispatch<'_> { + pub async fn dispatch( + &mut self, + command: ApiCommand, + identity: AuthId, + ) -> Result, ChronicleTransactionId)>, ApiError> { + // We can sort of get final on chain state here by using a map of subject to model + if let ApiResponse::Submission { .. } = self.api.dispatch(command, identity).await? { + loop { + let submission = self + .api + .notify_commit + .subscribe() + .recv() + .await + .expect("failed to receive response to submission"); + + if let SubmissionStage::Committed(commit, _) = submission { + break Ok(Some((commit.delta, commit.tx_id))) + } + if let SubmissionStage::NotCommitted((_, contradiction, _)) = submission { + panic!("Contradiction: {contradiction}"); + } + } + } else { + Ok(None) + } + } + } + + #[derive(Debug, Clone)] + struct SameUuid; + + impl UuidGen for SameUuid { + fn uuid() -> Uuid { + Uuid::parse_str("5a0ab5b8-eeb7-4812-9fe3-6dd69bd20cea").unwrap() + } + } + + async fn test_api<'a>() -> TestDispatch<'a> { + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + + let secrets = ChronicleSigning::new( + chronicle_secret_names(), + vec![ + (CHRONICLE_NAMESPACE.to_string(), ChronicleSecretsOptions::generate_in_memory()), + (BATCHER_NAMESPACE.to_string(), ChronicleSecretsOptions::generate_in_memory()), + ], + ) + .await + .unwrap(); + + let buf = async_stl_client::messages::Setting { + entries: vec![async_stl_client::messages::setting::Entry { + key: "chronicle.opa.policy_name".to_string(), + value: "allow_transactions".to_string(), + }], + } + .encode_to_vec(); + let setting_id = ( + chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.policy_name"), + buf, + ); + let buf = async_stl_client::messages::Setting { + entries: vec![async_stl_client::messages::setting::Entry { + key: "chronicle.opa.entrypoint".to_string(), + value: "allow_transactions.allowed_users".to_string(), + }], + } + .encode_to_vec(); + + let setting_entrypoint = ( + chronicle_protocol::settings::sawtooth_settings_address("chronicle.opa.entrypoint"), + buf, + ); + + let d = env!("CARGO_MANIFEST_DIR").to_owned() + "/../../policies/bundle.tar.gz"; + let bin = std::fs::read(d).unwrap(); + + let meta = PolicyMeta { + id: "allow_transactions".to_string(), + hash: hex::encode(Sha256::digest(&bin)), + policy_address: policy_address("allow_transactions"), + }; + + let embedded_tp = EmbeddedChronicleTp::new_with_state( + vec![ + setting_id, + setting_entrypoint, + (policy_address("allow_transactions"), bin), + (policy_meta_address("allow_transactions"), serde_json::to_vec(&meta).unwrap()), + ] + .into_iter() + .collect(), + ) + .unwrap(); + + let database = TemporaryDatabase::default(); + let pool = database.connection_pool().unwrap(); + + let liveness_check_interval = None; + + let dispatch = Api::new( + pool, + embedded_tp.ledger.clone(), + SameUuid, + secrets, + vec![], + Some("allow_transactions".to_owned()), + liveness_check_interval, + ) + .await + .unwrap(); + + TestDispatch { api: dispatch, _db: database, _tp: embedded_tp } + } + + fn get_api_cmd(command_line: &str) -> ApiCommand { + let cli = test_cli_model(); + let matches = cli.as_cmd().get_matches_from(command_line.split_whitespace()); + cli.matches(&matches).unwrap().unwrap() + } + + async fn parse_and_execute(command_line: &str, cli: CliModel) -> Box { + let mut api = test_api().await; + + let matches = cli.as_cmd().get_matches_from(command_line.split_whitespace()); + + let cmd = cli.matches(&matches).unwrap().unwrap(); + + let identity = AuthId::chronicle(); + + api.dispatch(cmd, identity).await.unwrap().unwrap().0 + } + + fn test_cli_model() -> CliModel { + CliModel::from( + ChronicleDomainDef::build("test") + .with_attribute_type("testString", None, crate::PrimitiveType::String) + .unwrap() + .with_attribute_type("testBool", None, crate::PrimitiveType::Bool) + .unwrap() + .with_attribute_type("testInt", None, crate::PrimitiveType::Int) + .unwrap() + .with_attribute_type("testJSON", None, crate::PrimitiveType::JSON) + .unwrap() + .with_activity("testActivity", None, |b| { + b.with_attribute("testString") + .unwrap() + .with_attribute("testBool") + .unwrap() + .with_attribute("testInt") + }) + .unwrap() + .with_agent("testAgent", None, |b| { + b.with_attribute("testString") + .unwrap() + .with_attribute("testBool") + .unwrap() + .with_attribute("testInt") + }) + .unwrap() + .with_entity("testEntity", None, |b| { + b.with_attribute("testString") + .unwrap() + .with_attribute("testBool") + .unwrap() + .with_attribute("testInt") + }) + .unwrap() + .build(), + ) + } + + #[tokio::test] + async fn agent_define() { + let command_line = r#"chronicle test-agent-agent define test_agent --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns "#; + + insta::assert_snapshot!( serde_json::to_string_pretty( &parse_and_execute(command_line, test_cli_model()).await.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1039,16 +981,16 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn agent_define_id() { - let id = ChronicleIri::from(common::prov::AgentId::from_external_id("test_agent")); - let command_line = format!( - r#"chronicle test-agent-agent define --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns --id {id} "# - ); + #[tokio::test] + async fn agent_define_id() { + let id = ChronicleIri::from(common::prov::AgentId::from_external_id("test_agent")); + let command_line = format!( + r#"chronicle test-agent-agent define --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns --id {id} "# + ); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &parse_and_execute(&command_line, test_cli_model()).await.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1077,18 +1019,18 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn agent_use() { - let mut api = test_api().await; + #[tokio::test] + async fn agent_use() { + let mut api = test_api().await; - // note, if you don't supply all three types of attribute this won't run - let command_line = r#"chronicle test-agent-agent define testagent --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 23 "#; + // note, if you don't supply all three types of attribute this won't run + let command_line = r#"chronicle test-agent-agent define testagent --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 23 "#; - let cmd = get_api_cmd(command_line); + let cmd = get_api_cmd(command_line); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &api.dispatch(cmd, AuthId::chronicle()).await.unwrap().unwrap().0.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1118,20 +1060,20 @@ pub mod test { } "###); - let id = AgentId::from_external_id("testagent"); + let id = AgentId::from_external_id("testagent"); - let command_line = format!(r#"chronicle test-agent-agent use --namespace testns {id} "#); - let cmd = get_api_cmd(&command_line); + let command_line = format!(r#"chronicle test-agent-agent use --namespace testns {id} "#); + let cmd = get_api_cmd(&command_line); - api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); + api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); - let id = ActivityId::from_external_id("testactivity"); - let command_line = format!( - r#"chronicle test-activity-activity start {id} --namespace testns --time 2014-07-08T09:10:11Z "# - ); - let cmd = get_api_cmd(&command_line); + let id = ActivityId::from_external_id("testactivity"); + let command_line = format!( + r#"chronicle test-activity-activity start {id} --namespace testns --time 2014-07-08T09:10:11Z "# + ); + let cmd = get_api_cmd(&command_line); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &api.dispatch(cmd, AuthId::chronicle()).await.unwrap().unwrap().0.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1169,14 +1111,14 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn entity_define() { - let command_line = r#"chronicle test-entity-entity define test_entity --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns "#; - let _delta = parse_and_execute(command_line, test_cli_model()); + #[tokio::test] + async fn entity_define() { + let command_line = r#"chronicle test-entity-entity define test_entity --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns "#; + let _delta = parse_and_execute(command_line, test_cli_model()); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &parse_and_execute(command_line, test_cli_model()).await.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1205,16 +1147,16 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn entity_define_id() { - let id = ChronicleIri::from(common::prov::EntityId::from_external_id("test_entity")); - let command_line = format!( - r#"chronicle test-entity-entity define --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns --id {id} "# - ); + #[tokio::test] + async fn entity_define_id() { + let id = ChronicleIri::from(common::prov::EntityId::from_external_id("test_entity")); + let command_line = format!( + r#"chronicle test-entity-entity define --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns --id {id} "# + ); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &parse_and_execute(&command_line, test_cli_model()).await.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1243,21 +1185,21 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn entity_derive_abstract() { - let mut api = test_api().await; + #[tokio::test] + async fn entity_derive_abstract() { + let mut api = test_api().await; - let generated_entity_id = EntityId::from_external_id("testgeneratedentity"); - let used_entity_id = EntityId::from_external_id("testusedentity"); + let generated_entity_id = EntityId::from_external_id("testgeneratedentity"); + let used_entity_id = EntityId::from_external_id("testusedentity"); - let command_line = format!( - r#"chronicle test-entity-entity derive {generated_entity_id} {used_entity_id} --namespace testns "# - ); - let cmd = get_api_cmd(&command_line); + let command_line = format!( + r#"chronicle test-entity-entity derive {generated_entity_id} {used_entity_id} --namespace testns "# + ); + let cmd = get_api_cmd(&command_line); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &api.dispatch(cmd, AuthId::chronicle()).await.unwrap().unwrap().0.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1289,21 +1231,21 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn entity_derive_primary_source() { - let mut api = test_api().await; + #[tokio::test] + async fn entity_derive_primary_source() { + let mut api = test_api().await; - let generated_entity_id = EntityId::from_external_id("testgeneratedentity"); - let used_entity_id = EntityId::from_external_id("testusedentity"); + let generated_entity_id = EntityId::from_external_id("testgeneratedentity"); + let used_entity_id = EntityId::from_external_id("testusedentity"); - let command_line = format!( - r#"chronicle test-entity-entity derive {generated_entity_id} {used_entity_id} --namespace testns --subtype primary-source "# - ); - let cmd = get_api_cmd(&command_line); + let command_line = format!( + r#"chronicle test-entity-entity derive {generated_entity_id} {used_entity_id} --namespace testns --subtype primary-source "# + ); + let cmd = get_api_cmd(&command_line); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &api.dispatch(cmd, AuthId::chronicle()).await.unwrap().unwrap().0.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1335,21 +1277,21 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn entity_derive_revision() { - let mut api = test_api().await; + #[tokio::test] + async fn entity_derive_revision() { + let mut api = test_api().await; - let generated_entity_id = EntityId::from_external_id("testgeneratedentity"); - let used_entity_id = EntityId::from_external_id("testusedentity"); + let generated_entity_id = EntityId::from_external_id("testgeneratedentity"); + let used_entity_id = EntityId::from_external_id("testusedentity"); - let command_line = format!( - r#"chronicle test-entity-entity derive {generated_entity_id} {used_entity_id} --namespace testns --subtype revision "# - ); - let cmd = get_api_cmd(&command_line); + let command_line = format!( + r#"chronicle test-entity-entity derive {generated_entity_id} {used_entity_id} --namespace testns --subtype revision "# + ); + let cmd = get_api_cmd(&command_line); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &api.dispatch(cmd, AuthId::chronicle()).await.unwrap().unwrap().0.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1381,21 +1323,21 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn entity_derive_quotation() { - let mut api = test_api().await; + #[tokio::test] + async fn entity_derive_quotation() { + let mut api = test_api().await; - let generated_entity_id = EntityId::from_external_id("testgeneratedentity"); - let used_entity_id = EntityId::from_external_id("testusedentity"); + let generated_entity_id = EntityId::from_external_id("testgeneratedentity"); + let used_entity_id = EntityId::from_external_id("testusedentity"); - let command_line = format!( - r#"chronicle test-entity-entity derive {generated_entity_id} {used_entity_id} --namespace testns --subtype quotation "# - ); - let cmd = get_api_cmd(&command_line); + let command_line = format!( + r#"chronicle test-entity-entity derive {generated_entity_id} {used_entity_id} --namespace testns --subtype quotation "# + ); + let cmd = get_api_cmd(&command_line); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &api.dispatch(cmd, AuthId::chronicle()).await.unwrap().unwrap().0.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1427,13 +1369,13 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn activity_define() { - let command_line = r#"chronicle test-activity-activity define test_activity --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns "#; + #[tokio::test] + async fn activity_define() { + let command_line = r#"chronicle test-activity-activity define test_activity --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns "#; - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &parse_and_execute(command_line, test_cli_model()).await.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1462,16 +1404,16 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn activity_define_id() { - let id = ChronicleIri::from(common::prov::ActivityId::from_external_id("test_activity")); - let command_line = format!( - r#"chronicle test-activity-activity define --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns --id {id} "# - ); + #[tokio::test] + async fn activity_define_id() { + let id = ChronicleIri::from(common::prov::ActivityId::from_external_id("test_activity")); + let command_line = format!( + r#"chronicle test-activity-activity define --test-bool-attr false --test-string-attr "test" --test-int-attr 23 --namespace testns --id {id} "# + ); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &parse_and_execute(&command_line, test_cli_model()).await.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1500,29 +1442,29 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn activity_start() { - let mut api = test_api().await; + #[tokio::test] + async fn activity_start() { + let mut api = test_api().await; - let command_line = r#"chronicle test-agent-agent define testagent --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 40 "#; - let cmd = get_api_cmd(command_line); + let command_line = r#"chronicle test-agent-agent define testagent --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 40 "#; + let cmd = get_api_cmd(command_line); - api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); + api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); - let id = ChronicleIri::from(AgentId::from_external_id("testagent")); - let command_line = format!(r#"chronicle test-agent-agent use --namespace testns {id} "#); - let cmd = get_api_cmd(&command_line); - api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); + let id = ChronicleIri::from(AgentId::from_external_id("testagent")); + let command_line = format!(r#"chronicle test-agent-agent use --namespace testns {id} "#); + let cmd = get_api_cmd(&command_line); + api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); - let id = ChronicleIri::from(ActivityId::from_external_id("testactivity")); - let command_line = format!( - r#"chronicle test-activity-activity start {id} --namespace testns --time 2014-07-08T09:10:11Z "# - ); - let cmd = get_api_cmd(&command_line); + let id = ChronicleIri::from(ActivityId::from_external_id("testactivity")); + let command_line = format!( + r#"chronicle test-activity-activity start {id} --namespace testns --time 2014-07-08T09:10:11Z "# + ); + let cmd = get_api_cmd(&command_line); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &api.dispatch(cmd, AuthId::chronicle()).await.unwrap().unwrap().0.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1560,37 +1502,37 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn activity_end() { - let mut api = test_api().await; + #[tokio::test] + async fn activity_end() { + let mut api = test_api().await; - let command_line = r#"chronicle test-agent-agent define testagent --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 40 "#; - let cmd = get_api_cmd(command_line); + let command_line = r#"chronicle test-agent-agent define testagent --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 40 "#; + let cmd = get_api_cmd(command_line); - api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); + api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); - let id = ChronicleIri::from(AgentId::from_external_id("testagent")); - let command_line = format!(r#"chronicle test-agent-agent use --namespace testns {id} "#); - let cmd = get_api_cmd(&command_line); - api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); + let id = ChronicleIri::from(AgentId::from_external_id("testagent")); + let command_line = format!(r#"chronicle test-agent-agent use --namespace testns {id} "#); + let cmd = get_api_cmd(&command_line); + api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); - let id = ChronicleIri::from(ActivityId::from_external_id("testactivity")); - let command_line = format!( - r#"chronicle test-activity-activity start {id} --namespace testns --time 2014-07-08T09:10:11Z "# - ); - let cmd = get_api_cmd(&command_line); - api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); + let id = ChronicleIri::from(ActivityId::from_external_id("testactivity")); + let command_line = format!( + r#"chronicle test-activity-activity start {id} --namespace testns --time 2014-07-08T09:10:11Z "# + ); + let cmd = get_api_cmd(&command_line); + api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); - // Should end the last opened activity - let id = ActivityId::from_external_id("testactivity"); - let command_line = format!( - r#"chronicle test-activity-activity end --namespace testns --time 2014-08-09T09:10:12Z {id} "# - ); - let cmd = get_api_cmd(&command_line); + // Should end the last opened activity + let id = ActivityId::from_external_id("testactivity"); + let command_line = format!( + r#"chronicle test-activity-activity end --namespace testns --time 2014-08-09T09:10:12Z {id} "# + ); + let cmd = get_api_cmd(&command_line); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &api.dispatch(cmd, AuthId::chronicle()).await.unwrap().unwrap().0.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1614,25 +1556,25 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn activity_generate() { - let mut api = test_api().await; + #[tokio::test] + async fn activity_generate() { + let mut api = test_api().await; - let command_line = r#"chronicle test-activity-activity define testactivity --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 40 "#; - let cmd = get_api_cmd(command_line); + let command_line = r#"chronicle test-activity-activity define testactivity --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 40 "#; + let cmd = get_api_cmd(command_line); - api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); + api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); - let activity_id = ActivityId::from_external_id("testactivity"); - let entity_id = EntityId::from_external_id("testentity"); - let command_line = format!( - r#"chronicle test-activity-activity generate --namespace testns {entity_id} {activity_id} "# - ); - let cmd = get_api_cmd(&command_line); + let activity_id = ActivityId::from_external_id("testactivity"); + let entity_id = EntityId::from_external_id("testentity"); + let command_line = format!( + r#"chronicle test-activity-activity generate --namespace testns {entity_id} {activity_id} "# + ); + let cmd = get_api_cmd(&command_line); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &api.dispatch(cmd, AuthId::chronicle()).await.unwrap().unwrap().0.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1657,35 +1599,35 @@ pub mod test { ] } "###); - } + } - #[tokio::test] - async fn activity_use() { - let mut api = test_api().await; + #[tokio::test] + async fn activity_use() { + let mut api = test_api().await; - let command_line = r#"chronicle test-agent-agent define testagent --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 40 "#; - let cmd = get_api_cmd(command_line); + let command_line = r#"chronicle test-agent-agent define testagent --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 40 "#; + let cmd = get_api_cmd(command_line); - api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); + api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); - let id = ChronicleIri::from(AgentId::from_external_id("testagent")); - let command_line = format!(r#"chronicle test-agent-agent use --namespace testns {id} "#); - let cmd = get_api_cmd(&command_line); - api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); + let id = ChronicleIri::from(AgentId::from_external_id("testagent")); + let command_line = format!(r#"chronicle test-agent-agent use --namespace testns {id} "#); + let cmd = get_api_cmd(&command_line); + api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); - let command_line = r#"chronicle test-activity-activity define testactivity --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 40 "#; - let cmd = get_api_cmd(command_line); - api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); + let command_line = r#"chronicle test-activity-activity define testactivity --namespace testns --test-string-attr "test" --test-bool-attr true --test-int-attr 40 "#; + let cmd = get_api_cmd(command_line); + api.dispatch(cmd, AuthId::chronicle()).await.unwrap(); - let activity_id = ActivityId::from_external_id("testactivity"); - let entity_id = EntityId::from_external_id("testentity"); - let command_line = format!( - r#"chronicle test-activity-activity use --namespace testns {entity_id} {activity_id} "# - ); + let activity_id = ActivityId::from_external_id("testactivity"); + let entity_id = EntityId::from_external_id("testentity"); + let command_line = format!( + r#"chronicle test-activity-activity use --namespace testns {entity_id} {activity_id} "# + ); - let cmd = get_api_cmd(&command_line); + let cmd = get_api_cmd(&command_line); - insta::assert_snapshot!( + insta::assert_snapshot!( serde_json::to_string_pretty( &api.dispatch(cmd, AuthId::chronicle()).await.unwrap().unwrap().0.to_json().compact_stable_order().await.unwrap() ).unwrap() , @r###" @@ -1724,5 +1666,5 @@ pub mod test { ] } "###); - } + } } diff --git a/crates/chronicle/src/bootstrap/opa.rs b/crates/chronicle/src/bootstrap/opa.rs index 34b078e0b..0522e3d92 100644 --- a/crates/chronicle/src/bootstrap/opa.rs +++ b/crates/chronicle/src/bootstrap/opa.rs @@ -1,96 +1,578 @@ use std::net::SocketAddr; use async_stl_client::zmq_client::{ - HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel, + HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel, }; use chronicle_protocol::{ - address::{FAMILY, VERSION}, - async_stl_client::ledger::SawtoothLedger, - settings::{read_opa_settings, OpaSettings, SettingsReader}, + address::{FAMILY, VERSION}, + async_stl_client::ledger::SawtoothLedger, + settings::{read_opa_settings, OpaSettings, SettingsReader}, }; use clap::ArgMatches; use common::opa::{ - CliPolicyLoader, ExecutorContext, PolicyLoader, SawtoothPolicyLoader, UrlPolicyLoader, + CliPolicyLoader, ExecutorContext, PolicyLoader, SawtoothPolicyLoader, UrlPolicyLoader, }; use tracing::{debug, instrument}; use super::CliError; +pub struct SawtoothPolicyLoader { + policy_id: String, + address: String, + policy: Option>, + entrypoint: String, + ledger: OpaLedger, +} + +impl SawtoothPolicyLoader { + pub fn new( + address: &SocketAddr, + policy_id: &str, + entrypoint: &str, + ) -> Result { + Ok(Self { + policy_id: policy_id.to_owned(), + address: String::default(), + policy: None, + entrypoint: entrypoint.to_owned(), + ledger: OpaLedger::new( + ZmqRequestResponseSawtoothChannel::new( + "sawtooth_policy", + &[address.to_owned()], + HighestBlockValidatorSelector, + )? + .retrying(), + FAMILY, + VERSION, + ), + }) + } + + fn sawtooth_address(&self, policy: impl AsRef) -> String { + policy_address(policy) + } + + #[instrument(level = "debug", skip(self))] + async fn load_bundle_from_chain(&mut self) -> Result, SawtoothCommunicationError> { + if let Some(policy) = self.policy.as_ref() { + return Ok(policy.clone()) + } + let load_policy_from = self.sawtooth_address(&self.policy_id); + debug!(load_policy_from=?load_policy_from); + + loop { + let res = self.ledger.get_state_entry(&load_policy_from).await; + + if let Err(res) = &res { + error!(error=?res, "Failed to load policy from chain"); + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + continue + } + + return Ok(res.unwrap()) + } + } +} + +#[async_trait::async_trait] +impl PolicyLoader for SawtoothPolicyLoader { + fn set_address(&mut self, address: &str) { + self.address = address.to_owned() + } + + fn set_rule_name(&mut self, name: &str) { + self.policy_id = name.to_owned() + } + + fn set_entrypoint(&mut self, entrypoint: &str) { + self.entrypoint = entrypoint.to_owned() + } + + fn get_address(&self) -> &str { + &self.address + } + + fn get_rule_name(&self) -> &str { + &self.policy_id + } + + fn get_entrypoint(&self) -> &str { + &self.entrypoint + } + + fn get_policy(&self) -> &[u8] { + self.policy.as_ref().unwrap() + } + + async fn load_policy(&mut self) -> Result<(), PolicyLoaderError> { + let bundle = self.load_bundle_from_chain().await?; + info!(fetched_policy_bytes=?bundle.len(), "Fetched policy"); + if bundle.is_empty() { + error!("Policy not found: {}", self.get_rule_name()); + return Err(PolicyLoaderError::MissingPolicy(self.get_rule_name().to_string())) + } + self.load_policy_from_bundle(&Bundle::from_bytes(&*bundle)?) + } + + fn load_policy_from_bytes(&mut self, policy: &[u8]) { + self.policy = Some(policy.to_vec()) + } + + fn hash(&self) -> String { + hex::encode(Sha256::digest(self.policy.as_ref().unwrap())) + } +} + +/// OPA policy loader for policies passed via CLI or embedded in Chronicle +#[derive(Clone, Default)] +pub struct CliPolicyLoader { + address: String, + rule_name: String, + entrypoint: String, + policy: Vec, +} + +impl CliPolicyLoader { + pub fn new() -> Self { + Self { ..Default::default() } + } + + #[instrument(level = "trace", skip(self), ret)] + async fn get_policy_from_file(&mut self) -> Result, PolicyLoaderError> { + let bundle = Bundle::from_file(self.get_address())?; + + self.load_policy_from_bundle(&bundle)?; + + Ok(self.get_policy().to_vec()) + } + + /// Create a loaded [`CliPolicyLoader`] from name of an embedded dev policy and entrypoint + pub fn from_embedded_policy(policy: &str, entrypoint: &str) -> Result { + if let Some(file) = EmbeddedOpaPolicies::get("bundle.tar.gz") { + let bytes = file.data.as_ref(); + let bundle = Bundle::from_bytes(bytes)?; + let mut loader = CliPolicyLoader::new(); + loader.set_rule_name(policy); + loader.set_entrypoint(entrypoint); + loader.load_policy_from_bundle(&bundle)?; + Ok(loader) + } else { + Err(PolicyLoaderError::EmbeddedOpaPolicies) + } + } + + /// Create a loaded [`CliPolicyLoader`] from an OPA policy's bytes and entrypoint + pub fn from_policy_bytes( + policy: &str, + entrypoint: &str, + bytes: &[u8], + ) -> Result { + let mut loader = CliPolicyLoader::new(); + loader.set_rule_name(policy); + loader.set_entrypoint(entrypoint); + let bundle = Bundle::from_bytes(bytes)?; + loader.load_policy_from_bundle(&bundle)?; + Ok(loader) + } +} + +#[async_trait::async_trait] +impl PolicyLoader for CliPolicyLoader { + fn set_address(&mut self, address: &str) { + self.address = address.to_owned() + } + + fn set_rule_name(&mut self, name: &str) { + self.rule_name = name.to_owned() + } + + fn set_entrypoint(&mut self, entrypoint: &str) { + self.entrypoint = entrypoint.to_owned() + } + + fn get_address(&self) -> &str { + &self.address + } + + fn get_rule_name(&self) -> &str { + &self.rule_name + } + + fn get_entrypoint(&self) -> &str { + &self.entrypoint + } + + fn get_policy(&self) -> &[u8] { + &self.policy + } + + fn load_policy_from_bytes(&mut self, policy: &[u8]) { + self.policy = policy.to_vec() + } + + async fn load_policy(&mut self) -> Result<(), PolicyLoaderError> { + self.policy = self.get_policy_from_file().await?; + Ok(()) + } + + fn hash(&self) -> String { + hex::encode(Sha256::digest(&self.policy)) + } +} + +#[derive(Clone, Default)] +pub struct UrlPolicyLoader { + policy_id: String, + address: String, + policy: Vec, + entrypoint: String, +} + +impl UrlPolicyLoader { + pub fn new(url: &str, policy_id: &str, entrypoint: &str) -> Self { + Self { + address: url.into(), + policy_id: policy_id.to_owned(), + entrypoint: entrypoint.to_owned(), + ..Default::default() + } + } +} + +#[async_trait::async_trait] +impl PolicyLoader for UrlPolicyLoader { + fn set_address(&mut self, address: &str) { + self.address = address.to_owned(); + } + + fn set_rule_name(&mut self, name: &str) { + self.policy_id = name.to_owned(); + } + + fn set_entrypoint(&mut self, entrypoint: &str) { + self.entrypoint = entrypoint.to_owned(); + } + + fn get_address(&self) -> &str { + &self.address + } + + fn get_rule_name(&self) -> &str { + &self.policy_id + } + + fn get_entrypoint(&self) -> &str { + &self.entrypoint + } + + fn get_policy(&self) -> &[u8] { + &self.policy + } + + fn load_policy_from_bytes(&mut self, policy: &[u8]) { + self.policy = policy.to_vec(); + } + + async fn load_policy(&mut self) -> Result<(), PolicyLoaderError> { + let address = &self.address; + let bundle = load_bytes_from_url(address).await?; + + info!(loaded_policy_bytes=?bundle.len(), "Loaded policy bundle"); + + if bundle.is_empty() { + error!("Policy not found: {}", self.get_rule_name()); + return Err(PolicyLoaderError::MissingPolicy(self.get_rule_name().to_string())) + } + + self.load_policy_from_bundle(&Bundle::from_bytes(&*bundle)?) + } + + fn hash(&self) -> String { + hex::encode(Sha256::digest(&self.policy)) + } +} + trait SetRuleOptions { - fn rule_addr(&mut self, options: &ArgMatches) -> Result<(), CliError>; - fn rule_entrypoint(&mut self, options: &ArgMatches) -> Result<(), CliError>; - fn set_addr_and_entrypoint(&mut self, options: &ArgMatches) -> Result<(), CliError> { - self.rule_addr(options)?; - self.rule_entrypoint(options)?; - Ok(()) - } + fn rule_addr(&mut self, options: &ArgMatches) -> Result<(), CliError>; + fn rule_entrypoint(&mut self, options: &ArgMatches) -> Result<(), CliError>; + fn set_addr_and_entrypoint(&mut self, options: &ArgMatches) -> Result<(), CliError> { + self.rule_addr(options)?; + self.rule_entrypoint(options)?; + Ok(()) + } } impl SetRuleOptions for CliPolicyLoader { - fn rule_addr(&mut self, options: &ArgMatches) -> Result<(), CliError> { - if let Some(val) = options.get_one::("opa-rule") { - self.set_address(val); - Ok(()) - } else { - Err(CliError::MissingArgument { - arg: "opa-rule".to_string(), - }) - } - } - - fn rule_entrypoint(&mut self, options: &ArgMatches) -> Result<(), CliError> { - if let Some(val) = options.get_one::("opa-entrypoint") { - self.set_entrypoint(val); - Ok(()) - } else { - Err(CliError::MissingArgument { - arg: "opa-entrypoint".to_string(), - }) - } - } + fn rule_addr(&mut self, options: &ArgMatches) -> Result<(), CliError> { + if let Some(val) = options.get_one::("opa-rule") { + self.set_address(val); + Ok(()) + } else { + Err(CliError::MissingArgument { arg: "opa-rule".to_string() }) + } + } + + fn rule_entrypoint(&mut self, options: &ArgMatches) -> Result<(), CliError> { + if let Some(val) = options.get_one::("opa-entrypoint") { + self.set_entrypoint(val); + Ok(()) + } else { + Err(CliError::MissingArgument { arg: "opa-entrypoint".to_string() }) + } + } } #[instrument()] pub async fn opa_executor_from_embedded_policy( - policy_name: &str, - entrypoint: &str, + policy_name: &str, + entrypoint: &str, ) -> Result { - let loader = CliPolicyLoader::from_embedded_policy(policy_name, entrypoint)?; - Ok(ExecutorContext::from_loader(&loader)?) + let loader = CliPolicyLoader::from_embedded_policy(policy_name, entrypoint)?; + Ok(ExecutorContext::from_loader(&loader)?) } #[instrument()] pub async fn opa_executor_from_sawtooth_settings( - validator_address: &Vec, + validator_address: &Vec, ) -> Result<(ExecutorContext, OpaSettings), CliError> { - let settings = SettingsReader::new(SawtoothLedger::new( - ZmqRequestResponseSawtoothChannel::new( - "opa_executor", - validator_address, - HighestBlockValidatorSelector, - )? - .retrying(), - FAMILY, - VERSION, - )); - let opa_settings = read_opa_settings(&settings).await?; - debug!(on_chain_opa_policy = ?opa_settings); - let mut loader = SawtoothPolicyLoader::new( - validator_address.get(0).unwrap(), - &opa_settings.policy_name, - &opa_settings.entrypoint, - )?; - loader.load_policy().await?; - Ok((ExecutorContext::from_loader(&loader)?, opa_settings)) + let settings = SettingsReader::new(SawtoothLedger::new( + ZmqRequestResponseSawtoothChannel::new( + "opa_executor", + validator_address, + HighestBlockValidatorSelector, + )? + .retrying(), + FAMILY, + VERSION, + )); + let opa_settings = read_opa_settings(&settings).await?; + debug!(on_chain_opa_policy = ?opa_settings); + let mut loader = SawtoothPolicyLoader::new( + validator_address.get(0).unwrap(), + &opa_settings.policy_name, + &opa_settings.entrypoint, + )?; + loader.load_policy().await?; + Ok((ExecutorContext::from_loader(&loader)?, opa_settings)) } #[instrument()] pub async fn opa_executor_from_url( - url: &str, - policy_name: &str, - entrypoint: &str, + url: &str, + policy_name: &str, + entrypoint: &str, ) -> Result { - let mut loader = UrlPolicyLoader::new(url, policy_name, entrypoint); - loader.load_policy().await?; - Ok(ExecutorContext::from_loader(&loader)?) + let mut loader = UrlPolicyLoader::new(url, policy_name, entrypoint); + loader.load_policy().await?; + Ok(ExecutorContext::from_loader(&loader)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::identity::IdentityContext; + use serde_json::Value; + use std::{collections::BTreeSet, io::Write}; + + fn chronicle_id() -> AuthId { + AuthId::chronicle() + } + + fn chronicle_user_opa_data() -> OpaData { + OpaData::Operation(IdentityContext::new( + AuthId::chronicle(), + Value::default(), + Value::default(), + )) + } + + fn allow_all_users() -> (String, String) { + let policy_name = "allow_transactions".to_string(); + let entrypoint = "allow_transactions/allowed_users".to_string(); + (policy_name, entrypoint) + } + + fn anonymous_user() -> AuthId { + AuthId::anonymous() + } + + fn anonymous_user_opa_data() -> OpaData { + OpaData::Operation(IdentityContext::new( + AuthId::anonymous(), + Value::default(), + Value::default(), + )) + } + + fn jwt_user() -> AuthId { + let claims = crate::identity::JwtClaims( + serde_json::json!({ + "sub": "abcdef", + }) + .as_object() + .unwrap() + .to_owned(), + ); + AuthId::from_jwt_claims(&claims, &BTreeSet::from(["sub".to_string()])).unwrap() + } + + fn jwt_user_opa_data() -> OpaData { + OpaData::Operation(IdentityContext::new(jwt_user(), Value::default(), Value::default())) + } + + #[test] + fn policy_loader_invalid_rule() { + let (_policy, entrypoint) = allow_all_users(); + let invalid_rule = "a_rule_that_does_not_exist"; + match CliPolicyLoader::from_embedded_policy(invalid_rule, &entrypoint) { + Err(e) => { + insta::assert_snapshot!(e.to_string(), @"Policy not found: a_rule_that_does_not_exist") + }, + _ => panic!("expected error"), + } + } + + #[tokio::test] + async fn opa_executor_allow_chronicle_users() -> Result<(), OpaExecutorError> { + let (policy, entrypoint) = allow_all_users(); + let loader = CliPolicyLoader::from_embedded_policy(&policy, &entrypoint)?; + let mut executor = WasmtimeOpaExecutor::from_loader(&loader).unwrap(); + assert!(executor.evaluate(&chronicle_id(), &chronicle_user_opa_data()).await.is_ok()); + Ok(()) + } + + #[tokio::test] + async fn opa_executor_allow_anonymous_users() -> Result<(), OpaExecutorError> { + let (policy, entrypoint) = allow_all_users(); + let loader = CliPolicyLoader::from_embedded_policy(&policy, &entrypoint)?; + let mut executor = WasmtimeOpaExecutor::from_loader(&loader).unwrap(); + executor.evaluate(&anonymous_user(), &anonymous_user_opa_data()).await.unwrap(); + Ok(()) + } + + #[tokio::test] + async fn opa_executor_allow_jwt_users() -> Result<(), OpaExecutorError> { + let (policy, entrypoint) = allow_all_users(); + let loader = CliPolicyLoader::from_embedded_policy(&policy, &entrypoint)?; + let mut executor = WasmtimeOpaExecutor::from_loader(&loader)?; + assert!(executor.evaluate(&jwt_user(), &jwt_user_opa_data()).await.is_ok()); + Ok(()) + } + + const BUNDLE_FILE: &str = "bundle.tar.gz"; + + fn embedded_policy_bundle() -> Result, PolicyLoaderError> { + EmbeddedOpaPolicies::get(BUNDLE_FILE) + .map(|file| file.data.to_vec()) + .ok_or(PolicyLoaderError::EmbeddedOpaPolicies) + } + + #[tokio::test] + async fn test_load_policy_from_http_url() { + let embedded_bundle = embedded_policy_bundle().unwrap(); + let (rule, entrypoint) = allow_all_users(); + + // Create a temporary HTTP server that serves a policy bundle + let mut server = mockito::Server::new_async().await; + + // Start the mock server and define the response + let _m = server + .mock("GET", "/bundle.tar.gz") + .with_body(&embedded_bundle) + .create_async() + .await; + + // Create the URL policy loader + let mut loader = + UrlPolicyLoader::new(&format!("{}/bundle.tar.gz", server.url()), &rule, &entrypoint); + + // Load the policy + let result = loader.load_policy().await; + assert!(result.is_ok()); + + let bundle = Bundle::from_bytes(&embedded_bundle).unwrap(); + + // Extract the policy from the bundle we embedded in the binary + let policy_from_embedded_bundle = bundle + .wasm_policies + .iter() + .find(|p| p.entrypoint == rule) + .map(|p| p.bytes.as_ref()) + .ok_or(PolicyLoaderError::MissingPolicy(rule.to_string())) + .unwrap(); + + // Get the loaded policy from the url + let policy_from_url = loader.get_policy(); + + assert_eq!(&policy_from_url, &policy_from_embedded_bundle); + } + + #[tokio::test] + async fn test_load_policy_from_file_url() { + let embedded_bundle = embedded_policy_bundle().unwrap(); + let (rule, entrypoint) = allow_all_users(); + + let temp_dir = tempfile::tempdir().unwrap(); + let policy_path = temp_dir.path().join("bundle.tar.gz"); + let mut file = std::fs::File::create(&policy_path).unwrap(); + file.write_all(&embedded_bundle).unwrap(); + + // Create the file URL policy loader + let file_url = format!("file://{}", policy_path.to_string_lossy()); + let mut loader = UrlPolicyLoader::new(&file_url, &rule, &entrypoint); + + // Load the policy + let result = loader.load_policy().await; + assert!(result.is_ok()); + + let bundle = Bundle::from_bytes(&embedded_bundle).unwrap(); + + // Extract the policy from the bundle we embedded in the binary + let policy_from_embedded_bundle = bundle + .wasm_policies + .iter() + .find(|p| p.entrypoint == rule) + .map(|p| p.bytes.as_ref()) + .ok_or(PolicyLoaderError::MissingPolicy(rule.to_string())) + .unwrap(); + + // Get the loaded policy from the file URL + let policy_from_file_url = loader.get_policy(); + + assert_eq!(policy_from_embedded_bundle, policy_from_file_url); + } + + #[tokio::test] + async fn test_load_policy_from_bare_path() { + let embedded_bundle = embedded_policy_bundle().unwrap(); + let (rule, entrypoint) = allow_all_users(); + + let temp_dir = tempfile::tempdir().unwrap(); + let policy_path = temp_dir.path().join("bundle.tar.gz"); + let mut file = std::fs::File::create(&policy_path).unwrap(); + file.write_all(&embedded_bundle).unwrap(); + + // Create the bare path policy loader + let mut loader = UrlPolicyLoader::new(&policy_path.to_string_lossy(), &rule, &entrypoint); + + // Load the policy + let result = loader.load_policy().await; + assert!(result.is_ok()); + + let bundle = Bundle::from_bytes(&embedded_bundle).unwrap(); + + // Extract the policy from the bundle we embedded in the binary + let policy_from_embedded_bundle = bundle + .wasm_policies + .iter() + .find(|p| p.entrypoint == rule) + .map(|p| p.bytes.as_ref()) + .ok_or(PolicyLoaderError::MissingPolicy(rule.to_string())) + .unwrap(); + + // Get the loaded policy from the url + let policy_from_bare_path_url = loader.get_policy(); + + assert_eq!(policy_from_embedded_bundle, policy_from_bare_path_url); + } } diff --git a/crates/chronicle/src/codegen/linter.rs b/crates/chronicle/src/codegen/linter.rs index d2d0e05ff..061e013c5 100644 --- a/crates/chronicle/src/codegen/linter.rs +++ b/crates/chronicle/src/codegen/linter.rs @@ -3,162 +3,149 @@ use jsonschema::{error::ValidationErrorKind, JSONSchema}; use std::{collections::HashSet, path::Path, process::exit}; fn bad_filename(filename: &str) { - println!("JSON or YAML filename extension required for {filename}"); - exit(2); + println!("JSON or YAML filename extension required for {filename}"); + exit(2); } fn build_json_validator(domain: &str) -> JSONSchema { - let json = match serde_json::from_str(domain) { - Ok(json) => json, - Err(error) => { - println!("failed to parse valid JSON from domain schema: {error}"); - exit(2); - } - }; - match JSONSchema::options() - .with_draft(jsonschema::Draft::Draft7) - .compile(&json) - { - Ok(json_schema) => json_schema, - Err(error) => { - println!("failed to interpret JSON as a domain schema: {error}"); - exit(2); - } - } + let json = match serde_json::from_str(domain) { + Ok(json) => json, + Err(error) => { + println!("failed to parse valid JSON from domain schema: {error}"); + exit(2); + }, + }; + match JSONSchema::options().with_draft(jsonschema::Draft::Draft7).compile(&json) { + Ok(json_schema) => json_schema, + Err(error) => { + println!("failed to interpret JSON as a domain schema: {error}"); + exit(2); + }, + } } fn check_json_valid(json_validator: &JSONSchema, json_data: &str) { - let json = match serde_json::from_str(json_data) { - Ok(json) => json, - Err(error) => { - println!("failed to parse valid JSON: {error}"); - exit(2); - } - }; - let validation = json_validator.validate(&json); - if let Err(errors) = validation { - for error in errors { - println!( - "path {} contains invalid data: {}", - error.instance_path, error - ); - if let ValidationErrorKind::Pattern { pattern } = error.kind { - match pattern.as_str() { - "^[A-Z][A-Z0-9_]*$" => { - println!("hint: start with capital letter, use only CAPITALS_UNDERSCORES_NUM8ER5"); - } - "[A-Z][A-Za-z0-9]*$" => { - println!("hint: start with capital letter, use only LettersAndNum8er5"); - } - _ => {} - } - } - } - exit(2); - } + let json = match serde_json::from_str(json_data) { + Ok(json) => json, + Err(error) => { + println!("failed to parse valid JSON: {error}"); + exit(2); + }, + }; + let validation = json_validator.validate(&json); + if let Err(errors) = validation { + for error in errors { + println!("path {} contains invalid data: {}", error.instance_path, error); + if let ValidationErrorKind::Pattern { pattern } = error.kind { + match pattern.as_str() { + "^[A-Z][A-Z0-9_]*$" => { + println!("hint: start with capital letter, use only CAPITALS_UNDERSCORES_NUM8ER5"); + }, + "[A-Z][A-Za-z0-9]*$" => { + println!("hint: start with capital letter, use only LettersAndNum8er5"); + }, + _ => {}, + } + } + } + exit(2); + } } fn check_yaml_valid(json_validator: &JSONSchema, yaml_data: &str) { - let json = match serde_yaml::from_str::(yaml_data) { - Ok(json) => json, - Err(error) => { - println!("failed to parse valid YAML: {error}"); - exit(2); - } - }; - let json_data = match serde_json::to_string(&json) { - Ok(json_data) => json_data, - Err(error) => { - println!("failed to write valid JSON from YAML: {error}"); - exit(2); - } - }; - check_json_valid(json_validator, &json_data); + let json = match serde_yaml::from_str::(yaml_data) { + Ok(json) => json, + Err(error) => { + println!("failed to parse valid YAML: {error}"); + exit(2); + }, + }; + let json_data = match serde_json::to_string(&json) { + Ok(json_data) => json_data, + Err(error) => { + println!("failed to write valid JSON from YAML: {error}"); + exit(2); + }, + }; + check_json_valid(json_validator, &json_data); } fn read_json_domain(data: &str) -> model::DomainFileInput { - match serde_json::from_str(data) { - Ok(domain) => domain, - Err(error) => { - println!("failed to interpret JSON as a domain: {error}"); - exit(2); - } - } + match serde_json::from_str(data) { + Ok(domain) => domain, + Err(error) => { + println!("failed to interpret JSON as a domain: {error}"); + exit(2); + }, + } } fn read_yaml_domain(data: &str) -> model::DomainFileInput { - match serde_yaml::from_str(data) { - Ok(domain) => domain, - Err(error) => { - println!("failed to interpret YAML as a domain: {error}"); - exit(2); - } - } + match serde_yaml::from_str(data) { + Ok(domain) => domain, + Err(error) => { + println!("failed to interpret YAML as a domain: {error}"); + exit(2); + }, + } } fn check_domain_attributes( - element: &str, - attributes: &HashSet, - named_resources: Vec<(&String, &model::ResourceDef)>, + element: &str, + attributes: &HashSet, + named_resources: Vec<(&String, &model::ResourceDef)>, ) { - let mut is_error = false; - for (name, resource) in named_resources { - for attribute in resource.attributes.iter() { - if !(attributes.contains(&attribute.0)) { - println!( - "{} named {} has unknown attribute {}", - element, name, attribute.0 - ); - is_error = true; - } - } - } - if is_error { - exit(2); - } + let mut is_error = false; + for (name, resource) in named_resources { + for attribute in resource.attributes.iter() { + if !(attributes.contains(&attribute.0)) { + println!("{} named {} has unknown attribute {}", element, name, attribute.0); + is_error = true; + } + } + } + if is_error { + exit(2); + } } fn check_domain(domain: model::DomainFileInput) { - let attributes = domain - .attributes - .keys() - .map(std::clone::Clone::clone) - .collect(); - check_domain_attributes("agent", &attributes, domain.agents.iter().collect()); - check_domain_attributes("entity", &attributes, domain.entities.iter().collect()); - check_domain_attributes("activity", &attributes, domain.activities.iter().collect()); + let attributes = domain.attributes.keys().map(std::clone::Clone::clone).collect(); + check_domain_attributes("agent", &attributes, domain.agents.iter().collect()); + check_domain_attributes("entity", &attributes, domain.entities.iter().collect()); + check_domain_attributes("activity", &attributes, domain.activities.iter().collect()); } pub fn check_files(filenames: Vec<&str>) { - let json_validator = build_json_validator(include_str!("../../schema/domain.json")); - for filename in filenames { - let filepath = Path::new(filename); - let data = match std::fs::read_to_string(filepath) { - Ok(data) => data, - Err(error) => { - println!("failed to read {filename}: {error}"); - exit(2); - } - }; - match filepath.extension() { - Some(extension) => { - match extension.to_ascii_lowercase().to_str() { - Some("json") | Some("jsn") => { - check_json_valid(&json_validator, data.as_str()); - check_domain(read_json_domain(&data)); - } - Some("yaml") | Some("yml") => { - check_yaml_valid(&json_validator, data.as_str()); - check_domain(read_yaml_domain(&data)); - } - _ => { - bad_filename(filename); - } - }; - } - None => { - bad_filename(filename); - } - }; - } + let json_validator = build_json_validator(include_str!("../../schema/domain.json")); + for filename in filenames { + let filepath = Path::new(filename); + let data = match std::fs::read_to_string(filepath) { + Ok(data) => data, + Err(error) => { + println!("failed to read {filename}: {error}"); + exit(2); + }, + }; + match filepath.extension() { + Some(extension) => { + match extension.to_ascii_lowercase().to_str() { + Some("json") | Some("jsn") => { + check_json_valid(&json_validator, data.as_str()); + check_domain(read_json_domain(&data)); + }, + Some("yaml") | Some("yml") => { + check_yaml_valid(&json_validator, data.as_str()); + check_domain(read_yaml_domain(&data)); + }, + _ => { + bad_filename(filename); + }, + }; + }, + None => { + bad_filename(filename); + }, + }; + } } diff --git a/crates/chronicle/src/codegen/mod.rs b/crates/chronicle/src/codegen/mod.rs index fb01b65ea..9be8a630f 100644 --- a/crates/chronicle/src/codegen/mod.rs +++ b/crates/chronicle/src/codegen/mod.rs @@ -10,1623 +10,1622 @@ pub use model::{AttributesTypeName, Builder, CliName, PrimitiveType, Property, T pub use self::model::{ActivityDef, AgentDef, AttributeDef, ChronicleDomainDef, EntityDef}; fn agent_union_type_name() -> String { - "Agent".to_owned() + "Agent".to_owned() } fn entity_union_type_name() -> String { - "Entity".to_owned() + "Entity".to_owned() } fn activity_union_type_name() -> String { - "Activity".to_owned() + "Activity".to_owned() } fn gen_attribute_scalars(attributes: &[AttributeDef]) -> rust::Tokens { - let graphql_new_type = &rust::import("chronicle::async_graphql", "NewType"); - let chronicle_json = &rust::import("chronicle::common::prov", "ChronicleJSON"); - quote! { - #(for attribute in attributes.iter() => - #[derive(Clone, #graphql_new_type)] - #[graphql(name = #_(#(attribute.as_scalar_type())), visible=true)] - #(if attribute.doc.is_some() { - #[doc = #_(#(attribute.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - pub struct #(attribute.as_scalar_type())(#(match attribute.primitive_type { - PrimitiveType::String => String, - PrimitiveType::Bool => bool, - PrimitiveType::Int => i32, - PrimitiveType::JSON => #chronicle_json, - } - )); - ) - } + let graphql_new_type = &rust::import("chronicle::async_graphql", "NewType"); + let chronicle_json = &rust::import("chronicle::common::prov", "ChronicleJSON"); + quote! { + #(for attribute in attributes.iter() => + #[derive(Clone, #graphql_new_type)] + #[graphql(name = #_(#(attribute.as_scalar_type())), visible=true)] + #(if attribute.doc.is_some() { + #[doc = #_(#(attribute.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + pub struct #(attribute.as_scalar_type())(#(match attribute.primitive_type { + PrimitiveType::String => String, + PrimitiveType::Bool => bool, + PrimitiveType::Int => i32, + PrimitiveType::JSON => #chronicle_json, + } + )); + ) + } } fn gen_association_and_attribution_unions() -> rust::Tokens { - let simple_object = &rust::import("chronicle::async_graphql", "SimpleObject").qualified(); - - let agent_ref_doc = include_str!("../../../../domain_docs/agent_ref.md"); - let association_doc = include_str!("../../../../domain_docs/association.md"); - let attribution_doc = include_str!("../../../../domain_docs/attribution.md"); - let entity_ref_doc = include_str!("../../../../domain_docs/entity_ref.md"); - - quote! { - - #[doc = #_(#agent_ref_doc)] - #[derive(#simple_object)] - pub struct AgentRef { - pub role: RoleType, - pub agent: Agent, - } - - #[doc = #_(#entity_ref_doc)] - #[derive(#simple_object)] - pub struct EntityRef { - pub role: RoleType, - pub entity: Entity, - } - - #[doc = #_(#association_doc)] - #[derive(#simple_object)] - pub struct Association { - pub responsible : AgentRef, - pub delegate: Option, - } - - #[doc = #_(#attribution_doc)] - #[derive(#simple_object)] - pub struct Attribution { - pub responsible : AgentRef, - } - - #[doc = #_(#attribution_doc)] - #[derive(#simple_object)] - pub struct Attributed { - pub attributed : EntityRef, - } - } + let simple_object = &rust::import("chronicle::async_graphql", "SimpleObject").qualified(); + + let agent_ref_doc = include_str!("../../../../domain_docs/agent_ref.md"); + let association_doc = include_str!("../../../../domain_docs/association.md"); + let attribution_doc = include_str!("../../../../domain_docs/attribution.md"); + let entity_ref_doc = include_str!("../../../../domain_docs/entity_ref.md"); + + quote! { + + #[doc = #_(#agent_ref_doc)] + #[derive(#simple_object)] + pub struct AgentRef { + pub role: RoleType, + pub agent: Agent, + } + + #[doc = #_(#entity_ref_doc)] + #[derive(#simple_object)] + pub struct EntityRef { + pub role: RoleType, + pub entity: Entity, + } + + #[doc = #_(#association_doc)] + #[derive(#simple_object)] + pub struct Association { + pub responsible : AgentRef, + pub delegate: Option, + } + + #[doc = #_(#attribution_doc)] + #[derive(#simple_object)] + pub struct Attribution { + pub responsible : AgentRef, + } + + #[doc = #_(#attribution_doc)] + #[derive(#simple_object)] + pub struct Attributed { + pub attributed : EntityRef, + } + } } fn gen_type_enums(domain: &ChronicleDomainDef) -> rust::Tokens { - let graphql_enum = &rust::import("chronicle::async_graphql", "Enum"); - let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); - let prov_role = &rust::import("chronicle::common::prov", "Role").qualified(); - - let activity_type_doc = include_str!("../../../../domain_docs/activity_type.md"); - let agent_type_doc = include_str!("../../../../domain_docs/agent_type.md"); - let entity_type_doc = include_str!("../../../../domain_docs/entity_type.md"); - let prov_activity_doc = include_str!("../../../../domain_docs/prov_activity.md"); - let prov_agent_doc = include_str!("../../../../domain_docs/prov_agent.md"); - let prov_entity_doc = include_str!("../../../../domain_docs/prov_entity.md"); - let role_doc = include_str!("../../../../domain_docs/role.md"); - let unspecified_doc = include_str!("../../../../domain_docs/unspecified.md"); - - quote! { - #[derive(#graphql_enum, Copy, Clone, Eq, PartialEq)] - #[allow(clippy::upper_case_acronyms)] - #[doc = #_(#role_doc)] - #(if domain.roles_doc.is_some() { - #[doc = ""] - #[doc = #_(#(domain.roles_doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - pub enum RoleType { - #[doc = #_(#unspecified_doc)] - Unspecified, - #(for role in domain.roles.iter() => - #[graphql(name = #_(#(role.preserve_inflection())), visible=true)] - #(role.as_type_name()), - ) - } - - #[allow(clippy::from_over_into)] - impl Into for Option<#prov_role> { - fn into(self) -> RoleType { - match self.as_ref().map(|x| x.as_str()) { - None => {RoleType::Unspecified} - #(for role in domain.roles.iter() => - Some(#_(#(role.preserve_inflection()))) => { RoleType::#(role.as_type_name())} - ) - Some(&_) => {RoleType::Unspecified} - } - } - } - - #[allow(clippy::from_over_into)] - impl Into> for RoleType { - fn into(self) -> Option<#prov_role> { - match self { - Self::Unspecified => None, - #(for role in domain.roles.iter() => - RoleType::#(role.as_type_name()) => { - Some(#prov_role::from(#_(#(role.preserve_inflection())))) - } - ) - } - } - } - - #[derive(#graphql_enum, Copy, Clone, Eq, PartialEq)] - #[doc = #_(#agent_type_doc)] - #[allow(clippy::upper_case_acronyms)] - #[allow(clippy::enum_variant_names)] - pub enum AgentType { - #[doc = #_(#prov_agent_doc)] - #[graphql(name = "ProvAgent", visible=true)] - ProvAgent, - #(for agent in domain.agents.iter() => - #(if agent.doc.is_some() { - #[doc = #_(#(agent.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #[graphql(name = #_(#(agent.as_type_name())), visible=true)] - #(agent.as_type_name()), - ) - } - - #[allow(clippy::from_over_into)] - impl Into> for AgentType { - fn into(self) -> Option<#domain_type_id> { - match self { - #(for agent in domain.agents.iter() => - AgentType::#(agent.as_type_name()) => Some(#domain_type_id::from_external_id(#_(#(agent.as_type_name())))), - ) - AgentType::ProvAgent => None - } - } - } - - #[derive(#graphql_enum, Copy, Clone, Eq, PartialEq)] - #[doc = #_(#entity_type_doc)] - #[allow(clippy::upper_case_acronyms)] - #[allow(clippy::enum_variant_names)] - pub enum EntityType { - #[doc = #_(#prov_entity_doc)] - #[graphql(name = "ProvEntity", visible=true)] - ProvEntity, - #(for entity in domain.entities.iter() => - #(if entity.doc.is_some() { - #[doc = #_(#(entity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #[graphql(name = #_(#(entity.as_type_name())), visible=true)] - #(entity.as_type_name()), - ) - } - - #[allow(clippy::from_over_into)] - impl Into> for EntityType { - fn into(self) -> Option<#domain_type_id> { - match self { - #(for entity in domain.entities.iter() => - EntityType::#(entity.as_type_name()) => Some(#domain_type_id::from_external_id(#_(#(entity.as_type_name())))), - ) - EntityType::ProvEntity => None - } - } - } - - #[derive(#graphql_enum, Copy, Clone, Eq, PartialEq)] - #[doc = #_(#activity_type_doc)] - #[allow(clippy::upper_case_acronyms)] - #[allow(clippy::enum_variant_names)] - pub enum ActivityType { - #[doc = #_(#prov_activity_doc)] - #[graphql(name = "ProvActivity", visible=true)] - ProvActivity, - #(for activity in domain.activities.iter() => - #(if activity.doc.is_some() { - #[doc = #_(#(activity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #[graphql(name = #_(#(activity.as_type_name())), visible=true)] - #(activity.as_type_name()), - ) - } - - #[allow(clippy::from_over_into)] - impl Into> for ActivityType { - fn into(self) -> Option<#domain_type_id> { - match self { - #(for activity in domain.activities.iter() => - ActivityType::#(activity.as_type_name()) => Some(#domain_type_id::from_external_id(#_(#(activity.as_type_name())))), - ) - ActivityType::ProvActivity => None - } - } - } - } + let graphql_enum = &rust::import("chronicle::async_graphql", "Enum"); + let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); + let prov_role = &rust::import("chronicle::common::prov", "Role").qualified(); + + let activity_type_doc = include_str!("../../../../domain_docs/activity_type.md"); + let agent_type_doc = include_str!("../../../../domain_docs/agent_type.md"); + let entity_type_doc = include_str!("../../../../domain_docs/entity_type.md"); + let prov_activity_doc = include_str!("../../../../domain_docs/prov_activity.md"); + let prov_agent_doc = include_str!("../../../../domain_docs/prov_agent.md"); + let prov_entity_doc = include_str!("../../../../domain_docs/prov_entity.md"); + let role_doc = include_str!("../../../../domain_docs/role.md"); + let unspecified_doc = include_str!("../../../../domain_docs/unspecified.md"); + + quote! { + #[derive(#graphql_enum, Copy, Clone, Eq, PartialEq)] + #[allow(clippy::upper_case_acronyms)] + #[doc = #_(#role_doc)] + #(if domain.roles_doc.is_some() { + #[doc = ""] + #[doc = #_(#(domain.roles_doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + pub enum RoleType { + #[doc = #_(#unspecified_doc)] + Unspecified, + #(for role in domain.roles.iter() => + #[graphql(name = #_(#(role.preserve_inflection())), visible=true)] + #(role.as_type_name()), + ) + } + + #[allow(clippy::from_over_into)] + impl Into for Option<#prov_role> { + fn into(self) -> RoleType { + match self.as_ref().map(|x| x.as_str()) { + None => {RoleType::Unspecified} + #(for role in domain.roles.iter() => + Some(#_(#(role.preserve_inflection()))) => { RoleType::#(role.as_type_name())} + ) + Some(&_) => {RoleType::Unspecified} + } + } + } + + #[allow(clippy::from_over_into)] + impl Into> for RoleType { + fn into(self) -> Option<#prov_role> { + match self { + Self::Unspecified => None, + #(for role in domain.roles.iter() => + RoleType::#(role.as_type_name()) => { + Some(#prov_role::from(#_(#(role.preserve_inflection())))) + } + ) + } + } + } + + #[derive(#graphql_enum, Copy, Clone, Eq, PartialEq)] + #[doc = #_(#agent_type_doc)] + #[allow(clippy::upper_case_acronyms)] + #[allow(clippy::enum_variant_names)] + pub enum AgentType { + #[doc = #_(#prov_agent_doc)] + #[graphql(name = "ProvAgent", visible=true)] + ProvAgent, + #(for agent in domain.agents.iter() => + #(if agent.doc.is_some() { + #[doc = #_(#(agent.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #[graphql(name = #_(#(agent.as_type_name())), visible=true)] + #(agent.as_type_name()), + ) + } + + #[allow(clippy::from_over_into)] + impl Into> for AgentType { + fn into(self) -> Option<#domain_type_id> { + match self { + #(for agent in domain.agents.iter() => + AgentType::#(agent.as_type_name()) => Some(#domain_type_id::from_external_id(#_(#(agent.as_type_name())))), + ) + AgentType::ProvAgent => None + } + } + } + + #[derive(#graphql_enum, Copy, Clone, Eq, PartialEq)] + #[doc = #_(#entity_type_doc)] + #[allow(clippy::upper_case_acronyms)] + #[allow(clippy::enum_variant_names)] + pub enum EntityType { + #[doc = #_(#prov_entity_doc)] + #[graphql(name = "ProvEntity", visible=true)] + ProvEntity, + #(for entity in domain.entities.iter() => + #(if entity.doc.is_some() { + #[doc = #_(#(entity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #[graphql(name = #_(#(entity.as_type_name())), visible=true)] + #(entity.as_type_name()), + ) + } + + #[allow(clippy::from_over_into)] + impl Into> for EntityType { + fn into(self) -> Option<#domain_type_id> { + match self { + #(for entity in domain.entities.iter() => + EntityType::#(entity.as_type_name()) => Some(#domain_type_id::from_external_id(#_(#(entity.as_type_name())))), + ) + EntityType::ProvEntity => None + } + } + } + + #[derive(#graphql_enum, Copy, Clone, Eq, PartialEq)] + #[doc = #_(#activity_type_doc)] + #[allow(clippy::upper_case_acronyms)] + #[allow(clippy::enum_variant_names)] + pub enum ActivityType { + #[doc = #_(#prov_activity_doc)] + #[graphql(name = "ProvActivity", visible=true)] + ProvActivity, + #(for activity in domain.activities.iter() => + #(if activity.doc.is_some() { + #[doc = #_(#(activity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #[graphql(name = #_(#(activity.as_type_name())), visible=true)] + #(activity.as_type_name()), + ) + } + + #[allow(clippy::from_over_into)] + impl Into> for ActivityType { + fn into(self) -> Option<#domain_type_id> { + match self { + #(for activity in domain.activities.iter() => + ActivityType::#(activity.as_type_name()) => Some(#domain_type_id::from_external_id(#_(#(activity.as_type_name())))), + ) + ActivityType::ProvActivity => None + } + } + } + } } fn gen_agent_union(agents: &[AgentDef]) -> rust::Tokens { - let union_macro = rust::import("chronicle::async_graphql", "Union").qualified(); - - let agent_doc = include_str!("../../../../domain_docs/agent.md"); - let prov_agent_doc = include_str!("../../../../domain_docs/prov_agent.md"); - - quote! { - #[doc = #_(#agent_doc)] - #[allow(clippy::enum_variant_names)] - #[allow(clippy::upper_case_acronyms)] - #[derive(#union_macro)] - pub enum Agent { - #[doc = #_(#prov_agent_doc)] - ProvAgent(ProvAgent), - #(for agent in agents => - #(if agent.doc.is_some() { - #[doc = #_(#(agent.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #(&agent.as_type_name())(#(&agent.as_type_name())), - ) - } - } + let union_macro = rust::import("chronicle::async_graphql", "Union").qualified(); + + let agent_doc = include_str!("../../../../domain_docs/agent.md"); + let prov_agent_doc = include_str!("../../../../domain_docs/prov_agent.md"); + + quote! { + #[doc = #_(#agent_doc)] + #[allow(clippy::enum_variant_names)] + #[allow(clippy::upper_case_acronyms)] + #[derive(#union_macro)] + pub enum Agent { + #[doc = #_(#prov_agent_doc)] + ProvAgent(ProvAgent), + #(for agent in agents => + #(if agent.doc.is_some() { + #[doc = #_(#(agent.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #(&agent.as_type_name())(#(&agent.as_type_name())), + ) + } + } } fn gen_entity_union(entities: &[EntityDef]) -> rust::Tokens { - let union_macro = rust::import("chronicle::async_graphql", "Union").qualified(); - - let entity_doc = include_str!("../../../../domain_docs/entity.md"); - let prov_entity_doc = include_str!("../../../../domain_docs/prov_entity.md"); - - quote! { - #[doc = #_(#entity_doc)] - #[allow(clippy::enum_variant_names)] - #[allow(clippy::upper_case_acronyms)] - #[derive(#union_macro)] - pub enum Entity { - #[doc = #_(#prov_entity_doc)] - ProvEntity(ProvEntity), - #(for entity in entities => - #(if entity.doc.is_some() { - #[doc = #_(#(entity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #(&entity.as_type_name())(#(&entity.as_type_name())), - ) - } - } + let union_macro = rust::import("chronicle::async_graphql", "Union").qualified(); + + let entity_doc = include_str!("../../../../domain_docs/entity.md"); + let prov_entity_doc = include_str!("../../../../domain_docs/prov_entity.md"); + + quote! { + #[doc = #_(#entity_doc)] + #[allow(clippy::enum_variant_names)] + #[allow(clippy::upper_case_acronyms)] + #[derive(#union_macro)] + pub enum Entity { + #[doc = #_(#prov_entity_doc)] + ProvEntity(ProvEntity), + #(for entity in entities => + #(if entity.doc.is_some() { + #[doc = #_(#(entity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #(&entity.as_type_name())(#(&entity.as_type_name())), + ) + } + } } fn gen_activity_union(activities: &[ActivityDef]) -> rust::Tokens { - let union_macro = rust::import("chronicle::async_graphql", "Union").qualified(); - - let activity_doc = include_str!("../../../../domain_docs/activity.md"); - let prov_activity_doc = include_str!("../../../../domain_docs/prov_activity.md"); - - quote! { - #[doc = #_(#activity_doc)] - #[allow(clippy::enum_variant_names)] - #[allow(clippy::upper_case_acronyms)] - #[derive(#union_macro)] - pub enum Activity { - #[doc = #_(#prov_activity_doc)] - ProvActivity(ProvActivity), - #(for activity in activities => - #(if activity.doc.is_some() { - #[doc = #_(#(activity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #(&activity.as_type_name())(#(&activity.as_type_name())), - ) - } - } + let union_macro = rust::import("chronicle::async_graphql", "Union").qualified(); + + let activity_doc = include_str!("../../../../domain_docs/activity.md"); + let prov_activity_doc = include_str!("../../../../domain_docs/prov_activity.md"); + + quote! { + #[doc = #_(#activity_doc)] + #[allow(clippy::enum_variant_names)] + #[allow(clippy::upper_case_acronyms)] + #[derive(#union_macro)] + pub enum Activity { + #[doc = #_(#prov_activity_doc)] + ProvActivity(ProvActivity), + #(for activity in activities => + #(if activity.doc.is_some() { + #[doc = #_(#(activity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #(&activity.as_type_name())(#(&activity.as_type_name())), + ) + } + } } fn gen_activity_definition(activity: &ActivityDef) -> rust::Tokens { - let abstract_activity = - &rust::import("chronicle::api::chronicle_graphql", "Activity").qualified(); - let activity_impl = &rust::import("chronicle::api::chronicle_graphql", "activity").qualified(); - let namespace = &rust::import("chronicle::api::chronicle_graphql", "Namespace").qualified(); - let activity_id = &rust::import("chronicle::common::prov", "ActivityId").qualified(); - let async_graphql_error_extensions = - &rust::import("chronicle::async_graphql", "ErrorExtensions").qualified(); - - let timezone = &rust::import("chronicle::chrono", "TimeZone").direct(); - let object = rust::import("chronicle::async_graphql", "Object").qualified(); - let async_result = &rust::import("chronicle::async_graphql", "Result").qualified(); - let context = &rust::import("chronicle::async_graphql", "Context").qualified(); - let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); - let date_time = &rust::import("chronicle::chrono", "DateTime"); - let utc = &rust::import("chronicle::chrono", "Utc"); - let chronicle_json = &rust::import("chronicle::common::prov", "ChronicleJSON"); - - let end_doc = include_str!("../../../../domain_docs/end.md"); - let external_id_doc = include_str!("../../../../domain_docs/external_id.md"); - let generated_doc = include_str!("../../../../domain_docs/generated.md"); - let id_doc = include_str!("../../../../domain_docs/id.md"); - let namespace_doc = include_str!("../../../../domain_docs/namespace.md"); - let start_doc = include_str!("../../../../domain_docs/start.md"); - let type_doc = include_str!("../../../../domain_docs/type.md"); - let used_doc = include_str!("../../../../domain_docs/used.md"); - let was_associated_with_doc = include_str!("../../../../domain_docs/was_associated_with.md"); - let was_informed_by_doc = include_str!("../../../../domain_docs/was_informed_by.md"); - - quote! { - #(register(activity_impl)) - - #[allow(clippy::upper_case_acronyms)] - pub struct #(&activity.as_type_name())(#abstract_activity); - - #[#object(name = #_(#(activity.as_type_name())))] - #(if activity.doc.is_some() { - #[doc = #_(#(activity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - impl #(&activity.as_type_name()) { - #[doc = #_(#id_doc)] - async fn id(&self) -> #activity_id { - #activity_id::from_external_id(&*self.0.external_id) - } - - #[doc = #_(#namespace_doc)] - async fn namespace<'a>(&self, ctx: &#context<'a>) -> #async_result<#namespace> { - #activity_impl::namespace(self.0.namespace_id, ctx).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#external_id_doc)] - async fn external_id(&self) -> &str { - &self.0.external_id - } - - #[doc = #_(#start_doc)] - async fn started(&self) -> Option<#date_time<#utc>> { - self.0.started.as_ref().map(|x| #timezone::from_utc_datetime(&#utc,x)) - } - - #[doc = #_(#end_doc)] - async fn ended(&self) -> Option<#date_time<#utc>> { - self.0.ended.as_ref().map(|x| #timezone::from_utc_datetime(&#utc,x)) - } - - #[doc = #_(#type_doc)] - #[graphql(name = "type")] - async fn typ(&self) -> Option<#domain_type_id> { - self.0.domaintype.as_deref().map(#domain_type_id::from_external_id) - } - - #[doc = #_(#was_associated_with_doc)] - async fn was_associated_with<'a>( - &self, - ctx: &#context<'a>, - ) -> #async_result> { - Ok( - #activity_impl::was_associated_with(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(|(r_agent, r_role, d_agent, d_role)| map_association_to_role(r_agent, d_agent, r_role, d_role)) - .collect(), - ) - } - - #[doc = #_(#used_doc)] - async fn used<'a>(&self, ctx: &#context<'a>) -> #async_result> { - Ok(#activity_impl::used(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(map_entity_to_domain_type) - .collect()) - } - - #[doc = #_(#was_informed_by_doc)] - async fn was_informed_by<'a>(&self, ctx: &#context<'a>) -> #async_result> { - Ok(#activity_impl::was_informed_by(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(map_activity_to_domain_type) - .collect()) - } - - #[doc = #_(#generated_doc)] - async fn generated<'a>( - &self, - ctx: &#context<'a>, - ) -> #async_result> { - Ok(#activity_impl::generated(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(map_entity_to_domain_type) - .collect()) - } - - #(for attribute in &activity.attributes => - #[graphql(name = #_(#(attribute.preserve_inflection())))] - #(if attribute.doc.is_some() { - #[doc = #_(#(attribute.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - async fn #(attribute.as_property())<'a>(&self, ctx: &#context<'a>) -> #async_result> { - Ok(#(match attribute.primitive_type { - PrimitiveType::String => - #activity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .and_then(|attr| attr.as_str().map(|attr| attr.to_owned())) - .map(#(attribute.as_scalar_type())), - PrimitiveType::Bool => - #activity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .and_then(|attr| attr.as_bool()) - .map(#(attribute.as_scalar_type())), - PrimitiveType::Int => - #activity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .and_then(|attr| attr.as_i64().map(|attr| attr as _)) - .map(#(attribute.as_scalar_type())), - PrimitiveType::JSON => - #activity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .map(#chronicle_json) - .map(#(attribute.as_scalar_type())) - })) - }) - } - } + let abstract_activity = + &rust::import("chronicle::api::chronicle_graphql", "Activity").qualified(); + let activity_impl = &rust::import("chronicle::api::chronicle_graphql", "activity").qualified(); + let namespace = &rust::import("chronicle::api::chronicle_graphql", "Namespace").qualified(); + let activity_id = &rust::import("chronicle::common::prov", "ActivityId").qualified(); + let async_graphql_error_extensions = + &rust::import("chronicle::async_graphql", "ErrorExtensions").qualified(); + + let timezone = &rust::import("chronicle::chrono", "TimeZone").direct(); + let object = rust::import("chronicle::async_graphql", "Object").qualified(); + let async_result = &rust::import("chronicle::async_graphql", "Result").qualified(); + let context = &rust::import("chronicle::async_graphql", "Context").qualified(); + let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); + let date_time = &rust::import("chronicle::chrono", "DateTime"); + let utc = &rust::import("chronicle::chrono", "Utc"); + let chronicle_json = &rust::import("chronicle::common::prov", "ChronicleJSON"); + + let end_doc = include_str!("../../../../domain_docs/end.md"); + let external_id_doc = include_str!("../../../../domain_docs/external_id.md"); + let generated_doc = include_str!("../../../../domain_docs/generated.md"); + let id_doc = include_str!("../../../../domain_docs/id.md"); + let namespace_doc = include_str!("../../../../domain_docs/namespace.md"); + let start_doc = include_str!("../../../../domain_docs/start.md"); + let type_doc = include_str!("../../../../domain_docs/type.md"); + let used_doc = include_str!("../../../../domain_docs/used.md"); + let was_associated_with_doc = include_str!("../../../../domain_docs/was_associated_with.md"); + let was_informed_by_doc = include_str!("../../../../domain_docs/was_informed_by.md"); + + quote! { + #(register(activity_impl)) + + #[allow(clippy::upper_case_acronyms)] + pub struct #(&activity.as_type_name())(#abstract_activity); + + #[#object(name = #_(#(activity.as_type_name())))] + #(if activity.doc.is_some() { + #[doc = #_(#(activity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + impl #(&activity.as_type_name()) { + #[doc = #_(#id_doc)] + async fn id(&self) -> #activity_id { + #activity_id::from_external_id(&*self.0.external_id) + } + + #[doc = #_(#namespace_doc)] + async fn namespace<'a>(&self, ctx: &#context<'a>) -> #async_result<#namespace> { + #activity_impl::namespace(self.0.namespace_id, ctx).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#external_id_doc)] + async fn external_id(&self) -> &str { + &self.0.external_id + } + + #[doc = #_(#start_doc)] + async fn started(&self) -> Option<#date_time<#utc>> { + self.0.started.as_ref().map(|x| #timezone::from_utc_datetime(&#utc,x)) + } + + #[doc = #_(#end_doc)] + async fn ended(&self) -> Option<#date_time<#utc>> { + self.0.ended.as_ref().map(|x| #timezone::from_utc_datetime(&#utc,x)) + } + + #[doc = #_(#type_doc)] + #[graphql(name = "type")] + async fn typ(&self) -> Option<#domain_type_id> { + self.0.domaintype.as_deref().map(#domain_type_id::from_external_id) + } + + #[doc = #_(#was_associated_with_doc)] + async fn was_associated_with<'a>( + &self, + ctx: &#context<'a>, + ) -> #async_result> { + Ok( + #activity_impl::was_associated_with(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(|(r_agent, r_role, d_agent, d_role)| map_association_to_role(r_agent, d_agent, r_role, d_role)) + .collect(), + ) + } + + #[doc = #_(#used_doc)] + async fn used<'a>(&self, ctx: &#context<'a>) -> #async_result> { + Ok(#activity_impl::used(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(map_entity_to_domain_type) + .collect()) + } + + #[doc = #_(#was_informed_by_doc)] + async fn was_informed_by<'a>(&self, ctx: &#context<'a>) -> #async_result> { + Ok(#activity_impl::was_informed_by(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(map_activity_to_domain_type) + .collect()) + } + + #[doc = #_(#generated_doc)] + async fn generated<'a>( + &self, + ctx: &#context<'a>, + ) -> #async_result> { + Ok(#activity_impl::generated(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(map_entity_to_domain_type) + .collect()) + } + + #(for attribute in &activity.attributes => + #[graphql(name = #_(#(attribute.preserve_inflection())))] + #(if attribute.doc.is_some() { + #[doc = #_(#(attribute.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + async fn #(attribute.as_property())<'a>(&self, ctx: &#context<'a>) -> #async_result> { + Ok(#(match attribute.primitive_type { + PrimitiveType::String => + #activity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .and_then(|attr| attr.as_str().map(|attr| attr.to_owned())) + .map(#(attribute.as_scalar_type())), + PrimitiveType::Bool => + #activity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .and_then(|attr| attr.as_bool()) + .map(#(attribute.as_scalar_type())), + PrimitiveType::Int => + #activity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .and_then(|attr| attr.as_i64().map(|attr| attr as _)) + .map(#(attribute.as_scalar_type())), + PrimitiveType::JSON => + #activity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .map(#chronicle_json) + .map(#(attribute.as_scalar_type())) + })) + }) + } + } } fn gen_entity_definition(entity: &EntityDef) -> rust::Tokens { - let abstract_entity = &rust::import("chronicle::api::chronicle_graphql", "Entity").qualified(); - let entity_impl = &rust::import("chronicle::api::chronicle_graphql", "entity").qualified(); - let namespace = &rust::import("chronicle::api::chronicle_graphql", "Namespace").qualified(); - let entity_id = &rust::import("chronicle::common::prov", "EntityId").qualified(); - - let object = rust::import("chronicle::async_graphql", "Object").qualified(); - let async_result = &rust::import("chronicle::async_graphql", "Result").qualified(); - let context = &rust::import("chronicle::async_graphql", "Context").qualified(); - let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); - let chronicle_json = &rust::import("chronicle::common::prov", "ChronicleJSON"); - let async_graphql_error_extensions = - &rust::import("chronicle::async_graphql", "ErrorExtensions").qualified(); - - let external_id_doc = include_str!("../../../../domain_docs/external_id.md"); - let had_primary_source_doc = include_str!("../../../../domain_docs/had_primary_source.md"); - let id_doc = include_str!("../../../../domain_docs/id.md"); - let namespace_doc = include_str!("../../../../domain_docs/namespace.md"); - let type_doc = include_str!("../../../../domain_docs/type.md"); - let was_attributed_to_doc = include_str!("../../../../domain_docs/was_attributed_to.md"); - let was_derived_from_doc = include_str!("../../../../domain_docs/was_derived_from.md"); - let was_generated_by_doc = include_str!("../../../../domain_docs/was_generated_by.md"); - let was_quoted_from_doc = include_str!("../../../../domain_docs/was_quoted_from.md"); - let was_revision_of_doc = include_str!("../../../../domain_docs/was_revision_of.md"); - - quote! { - - #(register(entity_impl)) - #[allow(clippy::upper_case_acronyms)] - pub struct #(&entity.as_type_name())(#abstract_entity); - - #[#object(name = #_(#(entity.as_type_name())))] - #(if entity.doc.is_some() { - #[doc = #_(#(entity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - impl #(&entity.as_type_name()){ - #[doc = #_(#id_doc)] - async fn id(&self) -> #entity_id { - #entity_id::from_external_id(&*self.0.external_id) - } - - #[doc = #_(#namespace_doc)] - async fn namespace<'a>(&self, ctx: &#context<'a>) -> #async_result<#namespace> { - #entity_impl::namespace(self.0.namespace_id, ctx).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#external_id_doc)] - async fn external_id(&self) -> &str { - &self.0.external_id - } - - #[doc = #_(#type_doc)] - #[graphql(name = "type")] - async fn typ(&self) -> Option<#domain_type_id> { - self.0.domaintype.as_deref().map(#domain_type_id::from_external_id) - } - - #[doc = #_(#was_attributed_to_doc)] - async fn was_attributed_to<'a>( - &self, - ctx: &#context<'a>, - ) -> #async_result> { - Ok( - #entity_impl::was_attributed_to(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(|(agent, role)| map_attribution_to_role(agent, role)) - .collect(), - ) - } - - #[doc = #_(#was_generated_by_doc)] - async fn was_generated_by<'a>( - &self, - ctx: &#context<'a>, - ) -> #async_result> { - Ok(#entity_impl::was_generated_by(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(map_activity_to_domain_type) - .collect()) - } - - #[doc = #_(#was_derived_from_doc)] - async fn was_derived_from<'a>(&self, ctx: &#context<'a>) -> #async_result> { - Ok(#entity_impl::was_derived_from(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(map_entity_to_domain_type) - .collect()) - } - - #[doc = #_(#had_primary_source_doc)] - async fn had_primary_source<'a>( - &self, - ctx: &#context<'a>, - ) -> #async_result> { - Ok( - #entity_impl::had_primary_source(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(map_entity_to_domain_type) - .collect(), - ) - } - - #[doc = #_(#was_revision_of_doc)] - async fn was_revision_of<'a>(&self, ctx: &#context<'a>) -> #async_result> { - Ok(#entity_impl::was_revision_of(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(map_entity_to_domain_type) - .collect()) - } - - #[doc = #_(#was_quoted_from_doc)] - async fn was_quoted_from<'a>(&self, ctx: &#context<'a>) -> #async_result> { - Ok(#entity_impl::was_quoted_from(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(map_entity_to_domain_type) - .collect()) - } - - #(for attribute in &entity.attributes => - #(if attribute.doc.is_some() { - #[doc = #_(#(attribute.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #[graphql(name = #_(#(attribute.preserve_inflection())))] - async fn #(attribute.as_property())<'a>(&self, ctx: &#context<'a>) -> #async_result> { - Ok(#(match attribute.primitive_type { - PrimitiveType::String => - #entity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .and_then(|attr| attr.as_str().map(|attr| attr.to_owned())) - .map(#(attribute.as_scalar_type())), - PrimitiveType::Bool => - #entity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .and_then(|attr| attr.as_bool()) - .map(#(attribute.as_scalar_type())), - PrimitiveType::Int => - #entity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .and_then(|attr| attr.as_i64().map(|attr| attr as _)) - .map(#(attribute.as_scalar_type())), - PrimitiveType::JSON => - #entity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .map(#chronicle_json) - .map(#(attribute.as_scalar_type())) - })) - }) - } - } + let abstract_entity = &rust::import("chronicle::api::chronicle_graphql", "Entity").qualified(); + let entity_impl = &rust::import("chronicle::api::chronicle_graphql", "entity").qualified(); + let namespace = &rust::import("chronicle::api::chronicle_graphql", "Namespace").qualified(); + let entity_id = &rust::import("chronicle::common::prov", "EntityId").qualified(); + + let object = rust::import("chronicle::async_graphql", "Object").qualified(); + let async_result = &rust::import("chronicle::async_graphql", "Result").qualified(); + let context = &rust::import("chronicle::async_graphql", "Context").qualified(); + let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); + let chronicle_json = &rust::import("chronicle::common::prov", "ChronicleJSON"); + let async_graphql_error_extensions = + &rust::import("chronicle::async_graphql", "ErrorExtensions").qualified(); + + let external_id_doc = include_str!("../../../../domain_docs/external_id.md"); + let had_primary_source_doc = include_str!("../../../../domain_docs/had_primary_source.md"); + let id_doc = include_str!("../../../../domain_docs/id.md"); + let namespace_doc = include_str!("../../../../domain_docs/namespace.md"); + let type_doc = include_str!("../../../../domain_docs/type.md"); + let was_attributed_to_doc = include_str!("../../../../domain_docs/was_attributed_to.md"); + let was_derived_from_doc = include_str!("../../../../domain_docs/was_derived_from.md"); + let was_generated_by_doc = include_str!("../../../../domain_docs/was_generated_by.md"); + let was_quoted_from_doc = include_str!("../../../../domain_docs/was_quoted_from.md"); + let was_revision_of_doc = include_str!("../../../../domain_docs/was_revision_of.md"); + + quote! { + + #(register(entity_impl)) + #[allow(clippy::upper_case_acronyms)] + pub struct #(&entity.as_type_name())(#abstract_entity); + + #[#object(name = #_(#(entity.as_type_name())))] + #(if entity.doc.is_some() { + #[doc = #_(#(entity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + impl #(&entity.as_type_name()){ + #[doc = #_(#id_doc)] + async fn id(&self) -> #entity_id { + #entity_id::from_external_id(&*self.0.external_id) + } + + #[doc = #_(#namespace_doc)] + async fn namespace<'a>(&self, ctx: &#context<'a>) -> #async_result<#namespace> { + #entity_impl::namespace(self.0.namespace_id, ctx).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#external_id_doc)] + async fn external_id(&self) -> &str { + &self.0.external_id + } + + #[doc = #_(#type_doc)] + #[graphql(name = "type")] + async fn typ(&self) -> Option<#domain_type_id> { + self.0.domaintype.as_deref().map(#domain_type_id::from_external_id) + } + + #[doc = #_(#was_attributed_to_doc)] + async fn was_attributed_to<'a>( + &self, + ctx: &#context<'a>, + ) -> #async_result> { + Ok( + #entity_impl::was_attributed_to(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(|(agent, role)| map_attribution_to_role(agent, role)) + .collect(), + ) + } + + #[doc = #_(#was_generated_by_doc)] + async fn was_generated_by<'a>( + &self, + ctx: &#context<'a>, + ) -> #async_result> { + Ok(#entity_impl::was_generated_by(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(map_activity_to_domain_type) + .collect()) + } + + #[doc = #_(#was_derived_from_doc)] + async fn was_derived_from<'a>(&self, ctx: &#context<'a>) -> #async_result> { + Ok(#entity_impl::was_derived_from(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(map_entity_to_domain_type) + .collect()) + } + + #[doc = #_(#had_primary_source_doc)] + async fn had_primary_source<'a>( + &self, + ctx: &#context<'a>, + ) -> #async_result> { + Ok( + #entity_impl::had_primary_source(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(map_entity_to_domain_type) + .collect(), + ) + } + + #[doc = #_(#was_revision_of_doc)] + async fn was_revision_of<'a>(&self, ctx: &#context<'a>) -> #async_result> { + Ok(#entity_impl::was_revision_of(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(map_entity_to_domain_type) + .collect()) + } + + #[doc = #_(#was_quoted_from_doc)] + async fn was_quoted_from<'a>(&self, ctx: &#context<'a>) -> #async_result> { + Ok(#entity_impl::was_quoted_from(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(map_entity_to_domain_type) + .collect()) + } + + #(for attribute in &entity.attributes => + #(if attribute.doc.is_some() { + #[doc = #_(#(attribute.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #[graphql(name = #_(#(attribute.preserve_inflection())))] + async fn #(attribute.as_property())<'a>(&self, ctx: &#context<'a>) -> #async_result> { + Ok(#(match attribute.primitive_type { + PrimitiveType::String => + #entity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .and_then(|attr| attr.as_str().map(|attr| attr.to_owned())) + .map(#(attribute.as_scalar_type())), + PrimitiveType::Bool => + #entity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .and_then(|attr| attr.as_bool()) + .map(#(attribute.as_scalar_type())), + PrimitiveType::Int => + #entity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .and_then(|attr| attr.as_i64().map(|attr| attr as _)) + .map(#(attribute.as_scalar_type())), + PrimitiveType::JSON => + #entity_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .map(#chronicle_json) + .map(#(attribute.as_scalar_type())) + })) + }) + } + } } fn gen_agent_definition(agent: &AgentDef) -> rust::Tokens { - let abstract_agent = &rust::import("chronicle::api::chronicle_graphql", "Agent").qualified(); - let agent_impl = &rust::import("chronicle::api::chronicle_graphql", "agent").qualified(); - let namespace = &rust::import("chronicle::api::chronicle_graphql", "Namespace").qualified(); - let identity = &rust::import("chronicle::api::chronicle_graphql", "Identity").qualified(); - let agent_union_type = &agent_union_type_name(); - let object = rust::import("chronicle::async_graphql", "Object").qualified(); - let async_result = &rust::import("chronicle::async_graphql", "Result").qualified(); - let context = &rust::import("chronicle::async_graphql", "Context").qualified(); - let agent_id = &rust::import("chronicle::common::prov", "AgentId"); - let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); - let chronicle_json = &rust::import("chronicle::common::prov", "ChronicleJSON"); - let async_graphql_error_extensions = - &rust::import("chronicle::async_graphql", "ErrorExtensions").qualified(); - - let acted_on_behalf_of_doc = include_str!("../../../../domain_docs/acted_on_behalf_of.md"); - let attribution_doc = include_str!("../../../../domain_docs/attribution.md"); - let external_id_doc = include_str!("../../../../domain_docs/external_id.md"); - let id_doc = include_str!("../../../../domain_docs/id.md"); - let identity_doc = include_str!("../../../../domain_docs/identity.md"); - let namespace_doc = include_str!("../../../../domain_docs/namespace.md"); - let type_doc = include_str!("../../../../domain_docs/type.md"); - - quote! { - - #(register(agent_impl)) - - #[allow(clippy::upper_case_acronyms)] - pub struct #(agent.as_type_name())(#abstract_agent); - - #[#object(name = #_(#(agent.as_type_name())))] - #(if agent.doc.is_some() { - #[doc = #_(#(agent.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - impl #(agent.as_type_name()) { - #[doc = #_(#id_doc)] - async fn id(&self) -> #agent_id { - #agent_id::from_external_id(&*self.0.external_id) - } - - #[doc = #_(#external_id_doc)] - async fn external_id(&self) -> &str { - &self.0.external_id - } - - #[doc = #_(#namespace_doc)] - async fn namespace<'a>(&self, ctx: &#context<'a>) -> #async_result<#namespace> { - #agent_impl::namespace(self.0.namespace_id, ctx).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#identity_doc)] - async fn identity<'a>(&self, ctx: &#context<'a>) -> #async_result> { - #agent_impl::identity(self.0.identity_id, ctx).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#acted_on_behalf_of_doc)] - async fn acted_on_behalf_of<'a>(&self, ctx: &#context<'a>) -> #async_result> { - Ok(#agent_impl::acted_on_behalf_of(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(|(agent,role)|(Self(agent),role)) - .map(|(agent,role)| AgentRef {agent : #agent_union_type::from(agent), role: role.into()}) - .collect()) - } - - #[doc = #_(#attribution_doc)] - async fn attribution<'a>(&self, ctx: &#context<'a>) -> #async_result> { - Ok(#agent_impl::attribution(self.0.id, ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .into_iter() - .map(|(entity, role)| { - map_attributed_to_role(entity, role) - }) - .collect()) - } - - #(for attribute in &agent.attributes => - #(if attribute.doc.is_some() { - #[doc = #_(#(attribute.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #[graphql(name = #_(#(attribute.preserve_inflection())))] - async fn #(attribute.as_property())<'a>(&self, ctx: &#context<'a>) -> #async_result> { - Ok(#(match attribute.primitive_type { - PrimitiveType::String => - #agent_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .and_then(|attr| attr.as_str().map(|attr| attr.to_owned())) - .map(#(attribute.as_scalar_type())), - PrimitiveType::Bool => - #agent_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .and_then(|attr| attr.as_bool()) - .map(#(attribute.as_scalar_type())), - PrimitiveType::Int => - #agent_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .and_then(|attr| attr.as_i64().map(|attr| attr as _)) - .map(#(attribute.as_scalar_type())), - PrimitiveType::JSON => - #agent_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .map(#chronicle_json) - .map(#(attribute.as_scalar_type())) - })) - }) - - #[doc = #_(#type_doc)] - #[graphql(name = "type")] - async fn typ(&self) -> Option<#domain_type_id> { - self.0.domaintype.as_deref().map(#domain_type_id::from_external_id) - } - } - } + let abstract_agent = &rust::import("chronicle::api::chronicle_graphql", "Agent").qualified(); + let agent_impl = &rust::import("chronicle::api::chronicle_graphql", "agent").qualified(); + let namespace = &rust::import("chronicle::api::chronicle_graphql", "Namespace").qualified(); + let identity = &rust::import("chronicle::api::chronicle_graphql", "Identity").qualified(); + let agent_union_type = &agent_union_type_name(); + let object = rust::import("chronicle::async_graphql", "Object").qualified(); + let async_result = &rust::import("chronicle::async_graphql", "Result").qualified(); + let context = &rust::import("chronicle::async_graphql", "Context").qualified(); + let agent_id = &rust::import("chronicle::common::prov", "AgentId"); + let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); + let chronicle_json = &rust::import("chronicle::common::prov", "ChronicleJSON"); + let async_graphql_error_extensions = + &rust::import("chronicle::async_graphql", "ErrorExtensions").qualified(); + + let acted_on_behalf_of_doc = include_str!("../../../../domain_docs/acted_on_behalf_of.md"); + let attribution_doc = include_str!("../../../../domain_docs/attribution.md"); + let external_id_doc = include_str!("../../../../domain_docs/external_id.md"); + let id_doc = include_str!("../../../../domain_docs/id.md"); + let identity_doc = include_str!("../../../../domain_docs/identity.md"); + let namespace_doc = include_str!("../../../../domain_docs/namespace.md"); + let type_doc = include_str!("../../../../domain_docs/type.md"); + + quote! { + + #(register(agent_impl)) + + #[allow(clippy::upper_case_acronyms)] + pub struct #(agent.as_type_name())(#abstract_agent); + + #[#object(name = #_(#(agent.as_type_name())))] + #(if agent.doc.is_some() { + #[doc = #_(#(agent.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + impl #(agent.as_type_name()) { + #[doc = #_(#id_doc)] + async fn id(&self) -> #agent_id { + #agent_id::from_external_id(&*self.0.external_id) + } + + #[doc = #_(#external_id_doc)] + async fn external_id(&self) -> &str { + &self.0.external_id + } + + #[doc = #_(#namespace_doc)] + async fn namespace<'a>(&self, ctx: &#context<'a>) -> #async_result<#namespace> { + #agent_impl::namespace(self.0.namespace_id, ctx).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#identity_doc)] + async fn identity<'a>(&self, ctx: &#context<'a>) -> #async_result> { + #agent_impl::identity(self.0.identity_id, ctx).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#acted_on_behalf_of_doc)] + async fn acted_on_behalf_of<'a>(&self, ctx: &#context<'a>) -> #async_result> { + Ok(#agent_impl::acted_on_behalf_of(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(|(agent,role)|(Self(agent),role)) + .map(|(agent,role)| AgentRef {agent : #agent_union_type::from(agent), role: role.into()}) + .collect()) + } + + #[doc = #_(#attribution_doc)] + async fn attribution<'a>(&self, ctx: &#context<'a>) -> #async_result> { + Ok(#agent_impl::attribution(self.0.id, ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .into_iter() + .map(|(entity, role)| { + map_attributed_to_role(entity, role) + }) + .collect()) + } + + #(for attribute in &agent.attributes => + #(if attribute.doc.is_some() { + #[doc = #_(#(attribute.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #[graphql(name = #_(#(attribute.preserve_inflection())))] + async fn #(attribute.as_property())<'a>(&self, ctx: &#context<'a>) -> #async_result> { + Ok(#(match attribute.primitive_type { + PrimitiveType::String => + #agent_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .and_then(|attr| attr.as_str().map(|attr| attr.to_owned())) + .map(#(attribute.as_scalar_type())), + PrimitiveType::Bool => + #agent_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .and_then(|attr| attr.as_bool()) + .map(#(attribute.as_scalar_type())), + PrimitiveType::Int => + #agent_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .and_then(|attr| attr.as_i64().map(|attr| attr as _)) + .map(#(attribute.as_scalar_type())), + PrimitiveType::JSON => + #agent_impl::load_attribute(self.0.id, #_(#(attribute.preserve_inflection())), ctx) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .map(#chronicle_json) + .map(#(attribute.as_scalar_type())) + })) + }) + + #[doc = #_(#type_doc)] + #[graphql(name = "type")] + async fn typ(&self) -> Option<#domain_type_id> { + self.0.domaintype.as_deref().map(#domain_type_id::from_external_id) + } + } + } } fn gen_abstract_prov_attributes() -> rust::Tokens { - let input_object = &rust::import("chronicle::async_graphql", "InputObject").qualified(); - let abstract_attributes = - &rust::import("chronicle::common::attributes", "Attributes").qualified(); - let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); - - quote! { - #[derive(#input_object, Clone)] - pub struct ProvAgentAttributes { - #[graphql(name = "type")] - pub typ: Option, - } - - #[allow(clippy::from_over_into)] - impl From for #abstract_attributes { - fn from(attributes: ProvAgentAttributes) -> Self { - Self { - typ: attributes.typ.map(#domain_type_id::from_external_id), - ..Default::default() - } - } - } - - #[derive(#input_object, Clone)] - pub struct ProvEntityAttributes { - #[graphql(name = "type")] - pub typ: Option, - } - - #[allow(clippy::from_over_into)] - impl From for #abstract_attributes { - fn from(attributes: ProvEntityAttributes) -> Self { - Self { - typ: attributes.typ.map(#domain_type_id::from_external_id), - ..Default::default() - } - } - } - #[derive(#input_object, Clone)] - pub struct ProvActivityAttributes { - #[graphql(name = "type")] - pub typ: Option, - } - - #[allow(clippy::from_over_into)] - impl From for #abstract_attributes { - fn from(attributes: ProvActivityAttributes) -> Self { - Self { - typ: attributes.typ.map(#domain_type_id::from_external_id), - ..Default::default() - } - } - } - } + let input_object = &rust::import("chronicle::async_graphql", "InputObject").qualified(); + let abstract_attributes = + &rust::import("chronicle::common::attributes", "Attributes").qualified(); + let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); + + quote! { + #[derive(#input_object, Clone)] + pub struct ProvAgentAttributes { + #[graphql(name = "type")] + pub typ: Option, + } + + #[allow(clippy::from_over_into)] + impl From for #abstract_attributes { + fn from(attributes: ProvAgentAttributes) -> Self { + Self { + typ: attributes.typ.map(#domain_type_id::from_external_id), + ..Default::default() + } + } + } + + #[derive(#input_object, Clone)] + pub struct ProvEntityAttributes { + #[graphql(name = "type")] + pub typ: Option, + } + + #[allow(clippy::from_over_into)] + impl From for #abstract_attributes { + fn from(attributes: ProvEntityAttributes) -> Self { + Self { + typ: attributes.typ.map(#domain_type_id::from_external_id), + ..Default::default() + } + } + } + #[derive(#input_object, Clone)] + pub struct ProvActivityAttributes { + #[graphql(name = "type")] + pub typ: Option, + } + + #[allow(clippy::from_over_into)] + impl From for #abstract_attributes { + fn from(attributes: ProvActivityAttributes) -> Self { + Self { + typ: attributes.typ.map(#domain_type_id::from_external_id), + ..Default::default() + } + } + } + } } fn gen_attribute_definition(typ: impl TypeName, attributes: &[AttributeDef]) -> rust::Tokens { - let abstract_attribute = - &rust::import("chronicle::common::attributes", "Attribute").qualified(); - let abstract_attributes = - &rust::import("chronicle::common::attributes", "Attributes").qualified(); - let input_object = rust::import("chronicle::async_graphql", "InputObject").qualified(); - let domain_type_id = rust::import("chronicle::common::prov", "DomaintypeId"); - let serde_value = &rust::import("chronicle::serde_json", "Value"); - - if attributes.is_empty() { - return quote! {}; - } - - quote! { - #[derive(#input_object)] - #[graphql(name = #_(#(typ.attributes_type_name_preserve_inflection())))] - pub struct #(typ.attributes_type_name_preserve_inflection()) { - #(for attribute in attributes => - #[graphql(name = #_(#(attribute.preserve_inflection())))] - pub #(&attribute.as_property()): #( - match attribute.primitive_type { - PrimitiveType::String => String, - PrimitiveType::Bool => bool, - PrimitiveType::Int => i32, - PrimitiveType::JSON => Value, - }), - ) - } - - - #[allow(clippy::from_over_into)] - #[allow(clippy::useless_conversion)] - impl From<#(typ.attributes_type_name_preserve_inflection())> for #abstract_attributes{ - fn from(attributes: #(typ.attributes_type_name_preserve_inflection())) -> Self { - #abstract_attributes { - typ: Some(#domain_type_id::from_external_id(#_(#(typ.as_type_name())))), - attributes: vec![ - #(for attribute in attributes => - (#_(#(&attribute.preserve_inflection())).to_owned() , - #abstract_attribute::new(#_(#(&attribute.preserve_inflection())), - #serde_value::from(attributes.#(&attribute.as_property())))), - ) - ].into_iter().collect(), - } - } - } - } + let abstract_attribute = + &rust::import("chronicle::common::attributes", "Attribute").qualified(); + let abstract_attributes = + &rust::import("chronicle::common::attributes", "Attributes").qualified(); + let input_object = rust::import("chronicle::async_graphql", "InputObject").qualified(); + let domain_type_id = rust::import("chronicle::common::prov", "DomaintypeId"); + let serde_value = &rust::import("chronicle::serde_json", "Value"); + + if attributes.is_empty() { + return quote! {} + } + + quote! { + #[derive(#input_object)] + #[graphql(name = #_(#(typ.attributes_type_name_preserve_inflection())))] + pub struct #(typ.attributes_type_name_preserve_inflection()) { + #(for attribute in attributes => + #[graphql(name = #_(#(attribute.preserve_inflection())))] + pub #(&attribute.as_property()): #( + match attribute.primitive_type { + PrimitiveType::String => String, + PrimitiveType::Bool => bool, + PrimitiveType::Int => i32, + PrimitiveType::JSON => Value, + }), + ) + } + + + #[allow(clippy::from_over_into)] + #[allow(clippy::useless_conversion)] + impl From<#(typ.attributes_type_name_preserve_inflection())> for #abstract_attributes{ + fn from(attributes: #(typ.attributes_type_name_preserve_inflection())) -> Self { + #abstract_attributes { + typ: Some(#domain_type_id::from_external_id(#_(#(typ.as_type_name())))), + attributes: vec![ + #(for attribute in attributes => + (#_(#(&attribute.preserve_inflection())).to_owned() , + #abstract_attribute::new(#_(#(&attribute.preserve_inflection())), + #serde_value::from(attributes.#(&attribute.as_property())))), + ) + ].into_iter().collect(), + } + } + } + } } fn gen_mappers(domain: &ChronicleDomainDef) -> rust::Tokens { - let agent_impl = &rust::import("chronicle::api::chronicle_graphql", "Agent").qualified(); - let role = &rust::import("chronicle::common::prov", "Role").qualified(); - let entity_impl = &rust::import("chronicle::api::chronicle_graphql", "Entity").qualified(); - let activity_impl = &rust::import("chronicle::api::chronicle_graphql", "Activity").qualified(); - - quote! { - #[allow(clippy::match_single_binding)] - fn map_agent_to_domain_type(agent: #agent_impl) -> #(agent_union_type_name()) { - match agent.domaintype.as_deref() { - #(for agent in domain.agents.iter() => - Some(#_(#(&agent.as_type_name()))) => #(agent_union_type_name())::#(&agent.as_type_name())( - #(&agent.as_type_name())(agent) - ), - ) - _ => #(agent_union_type_name())::ProvAgent(ProvAgent(agent)) - } - } - /// Maps to an association, missing roles, or ones that are no longer specified in the domain will be returned as RoleType::Unspecified - fn map_association_to_role(responsible: #agent_impl, delegate: Option<#agent_impl>, responsible_role: Option<#role>, delegate_role: Option<#role>) -> Association { - Association { - responsible: match responsible_role.as_ref().map(|x| x.as_str()) { - None => { - AgentRef{ agent: map_agent_to_domain_type(responsible), role: RoleType::Unspecified } - }, - #(for role in domain.roles.iter() => - Some(#_(#(&role.preserve_inflection()))) => {AgentRef { role: RoleType::#(role.as_type_name()), - agent: map_agent_to_domain_type(responsible) - }} - ) - Some(&_) => { - AgentRef{ agent: map_agent_to_domain_type(responsible), role: RoleType::Unspecified } - } - }, - delegate: match (delegate,delegate_role.as_ref().map(|x| x.as_str())) { - (None,_) => None, - (Some(delegate), None) => { - Some(AgentRef{role: RoleType::Unspecified, agent: map_agent_to_domain_type(delegate)}) - }, - #(for role in domain.roles.iter() => - (Some(delegate),Some(#_(#(&role.preserve_inflection())))) => { - Some(AgentRef{ role: RoleType::#(role.as_type_name()), - agent: map_agent_to_domain_type(delegate) - })}) - (Some(delegate), Some(&_)) => { - Some(AgentRef{ role: RoleType::Unspecified, agent: map_agent_to_domain_type(delegate)}) - }, - } - } - } - /// Maps an `Agent` and, if applicable, `Role` to an attribution. Missing roles, or ones that are no longer specified in the domain, will be returned as `RoleType::Unspecified` - fn map_attribution_to_role(responsible: #agent_impl, responsible_role: Option<#role>) -> Attribution { - Attribution { - responsible: match responsible_role.as_ref().map(|x| x.as_str()) { - None => { - AgentRef{ agent: map_agent_to_domain_type(responsible), role: RoleType::Unspecified } - }, - #(for role in domain.roles.iter() => - Some(#_(#(&role.preserve_inflection()))) => {AgentRef { role: RoleType::#(role.as_type_name()), - agent: map_agent_to_domain_type(responsible) - }} - ) - Some(&_) => { - AgentRef{ agent: map_agent_to_domain_type(responsible), role: RoleType::Unspecified } - } - } - } - } - /// Maps to an attribution. Missing roles, or ones that are no longer specified in the domain, will be returned as `RoleType::Unspecified` - fn map_attributed_to_role(entity: #entity_impl, attributed_role: Option<#role>) -> Attributed { - Attributed { - attributed: match attributed_role.as_ref().map(|x| x.as_str()) { - None => { - EntityRef{ entity: map_entity_to_domain_type(entity), role: RoleType::Unspecified } - }, - #(for role in domain.roles.iter() => - Some(#_(#(&role.preserve_inflection()))) => {EntityRef { role: RoleType::#(role.as_type_name()), - entity: map_entity_to_domain_type(entity) - }} - ) - Some(&_) => { - EntityRef{ entity: map_entity_to_domain_type(entity), role: RoleType::Unspecified } - } - } - } - } - #[allow(clippy::match_single_binding)] - fn map_activity_to_domain_type(activity: #activity_impl) -> #(activity_union_type_name()) { - match activity.domaintype.as_deref() { - #(for activity in domain.activities.iter() => - Some(#_(#(&activity.as_type_name()))) => #(activity_union_type_name())::#(&activity.as_type_name())( - #(&activity.as_type_name())(activity) - ), - ) - _ => #(activity_union_type_name())::ProvActivity(ProvActivity(activity)) - } - } - #[allow(clippy::match_single_binding)] - fn map_entity_to_domain_type(entity: #entity_impl) -> #(entity_union_type_name()) { - match entity.domaintype.as_deref() { - #(for entity in domain.entities.iter() => - Some(#_(#(&entity.as_type_name()))) => #(entity_union_type_name())::#(&entity.as_type_name())( - #(entity.as_type_name())(entity) - ), - ) - _ => #(entity_union_type_name())::ProvEntity(ProvEntity(entity)) - } - } - } + let agent_impl = &rust::import("chronicle::api::chronicle_graphql", "Agent").qualified(); + let role = &rust::import("chronicle::common::prov", "Role").qualified(); + let entity_impl = &rust::import("chronicle::api::chronicle_graphql", "Entity").qualified(); + let activity_impl = &rust::import("chronicle::api::chronicle_graphql", "Activity").qualified(); + + quote! { + #[allow(clippy::match_single_binding)] + fn map_agent_to_domain_type(agent: #agent_impl) -> #(agent_union_type_name()) { + match agent.domaintype.as_deref() { + #(for agent in domain.agents.iter() => + Some(#_(#(&agent.as_type_name()))) => #(agent_union_type_name())::#(&agent.as_type_name())( + #(&agent.as_type_name())(agent) + ), + ) + _ => #(agent_union_type_name())::ProvAgent(ProvAgent(agent)) + } + } + /// Maps to an association, missing roles, or ones that are no longer specified in the domain will be returned as RoleType::Unspecified + fn map_association_to_role(responsible: #agent_impl, delegate: Option<#agent_impl>, responsible_role: Option<#role>, delegate_role: Option<#role>) -> Association { + Association { + responsible: match responsible_role.as_ref().map(|x| x.as_str()) { + None => { + AgentRef{ agent: map_agent_to_domain_type(responsible), role: RoleType::Unspecified } + }, + #(for role in domain.roles.iter() => + Some(#_(#(&role.preserve_inflection()))) => {AgentRef { role: RoleType::#(role.as_type_name()), + agent: map_agent_to_domain_type(responsible) + }} + ) + Some(&_) => { + AgentRef{ agent: map_agent_to_domain_type(responsible), role: RoleType::Unspecified } + } + }, + delegate: match (delegate,delegate_role.as_ref().map(|x| x.as_str())) { + (None,_) => None, + (Some(delegate), None) => { + Some(AgentRef{role: RoleType::Unspecified, agent: map_agent_to_domain_type(delegate)}) + }, + #(for role in domain.roles.iter() => + (Some(delegate),Some(#_(#(&role.preserve_inflection())))) => { + Some(AgentRef{ role: RoleType::#(role.as_type_name()), + agent: map_agent_to_domain_type(delegate) + })}) + (Some(delegate), Some(&_)) => { + Some(AgentRef{ role: RoleType::Unspecified, agent: map_agent_to_domain_type(delegate)}) + }, + } + } + } + /// Maps an `Agent` and, if applicable, `Role` to an attribution. Missing roles, or ones that are no longer specified in the domain, will be returned as `RoleType::Unspecified` + fn map_attribution_to_role(responsible: #agent_impl, responsible_role: Option<#role>) -> Attribution { + Attribution { + responsible: match responsible_role.as_ref().map(|x| x.as_str()) { + None => { + AgentRef{ agent: map_agent_to_domain_type(responsible), role: RoleType::Unspecified } + }, + #(for role in domain.roles.iter() => + Some(#_(#(&role.preserve_inflection()))) => {AgentRef { role: RoleType::#(role.as_type_name()), + agent: map_agent_to_domain_type(responsible) + }} + ) + Some(&_) => { + AgentRef{ agent: map_agent_to_domain_type(responsible), role: RoleType::Unspecified } + } + } + } + } + /// Maps to an attribution. Missing roles, or ones that are no longer specified in the domain, will be returned as `RoleType::Unspecified` + fn map_attributed_to_role(entity: #entity_impl, attributed_role: Option<#role>) -> Attributed { + Attributed { + attributed: match attributed_role.as_ref().map(|x| x.as_str()) { + None => { + EntityRef{ entity: map_entity_to_domain_type(entity), role: RoleType::Unspecified } + }, + #(for role in domain.roles.iter() => + Some(#_(#(&role.preserve_inflection()))) => {EntityRef { role: RoleType::#(role.as_type_name()), + entity: map_entity_to_domain_type(entity) + }} + ) + Some(&_) => { + EntityRef{ entity: map_entity_to_domain_type(entity), role: RoleType::Unspecified } + } + } + } + } + #[allow(clippy::match_single_binding)] + fn map_activity_to_domain_type(activity: #activity_impl) -> #(activity_union_type_name()) { + match activity.domaintype.as_deref() { + #(for activity in domain.activities.iter() => + Some(#_(#(&activity.as_type_name()))) => #(activity_union_type_name())::#(&activity.as_type_name())( + #(&activity.as_type_name())(activity) + ), + ) + _ => #(activity_union_type_name())::ProvActivity(ProvActivity(activity)) + } + } + #[allow(clippy::match_single_binding)] + fn map_entity_to_domain_type(entity: #entity_impl) -> #(entity_union_type_name()) { + match entity.domaintype.as_deref() { + #(for entity in domain.entities.iter() => + Some(#_(#(&entity.as_type_name()))) => #(entity_union_type_name())::#(&entity.as_type_name())( + #(entity.as_type_name())(entity) + ), + ) + _ => #(entity_union_type_name())::ProvEntity(ProvEntity(entity)) + } + } + } } fn gen_query() -> rust::Tokens { - let query_impl = &rust::import("chronicle::api::chronicle_graphql", "query").qualified(); - - let graphql_object = &rust::import("chronicle::async_graphql", "Object"); - let graphql_result = &rust::import("chronicle::async_graphql", "Result"); - let graphql_id = &rust::import("chronicle::async_graphql", "ID"); - let graphql_context = &rust::import("chronicle::async_graphql", "Context"); - let graphql_connection = &rust::import("chronicle::async_graphql::connection", "Connection"); - let async_graphql_error_extensions = - &rust::import("chronicle::async_graphql", "ErrorExtensions").qualified(); - - let agent_id = &rust::import("chronicle::common::prov", "AgentIdOrExternal"); - let entity_id = &rust::import("chronicle::common::prov", "EntityIdOrExternal"); - let activity_id = &rust::import("chronicle::common::prov", "ActivityIdOrExternal"); - let empty_fields = - &rust::import("chronicle::async_graphql::connection", "EmptyFields").qualified(); - - let timeline_order = - &rust::import("chronicle::api::chronicle_graphql", "TimelineOrder").qualified(); - - let activities_by_type_doc = include_str!("../../../../domain_docs/activities_by_type.md"); - let activity_by_id_doc = include_str!("../../../../domain_docs/activity_by_id.md"); - let activity_timeline_doc = include_str!("../../../../domain_docs/activity_timeline.md"); - let agent_by_id_doc = include_str!("../../../../domain_docs/agent_by_id.md"); - let agents_by_type_doc = include_str!("../../../../domain_docs/agents_by_type.md"); - let entities_by_type_doc = include_str!("../../../../domain_docs/entities_by_type.md"); - let entity_by_id_doc = include_str!("../../../../domain_docs/entity_by_id.md"); - - quote! { - #[derive(Copy, Clone)] - pub struct Query; - - #[#graphql_object] - impl Query { - - #[doc = #_(#activity_timeline_doc)] - #[allow(clippy::too_many_arguments)] - pub async fn activity_timeline<'a>( - &self, - ctx: &#graphql_context<'a>, - activity_types: Option>, - for_entity: Option>, - for_agent: Option>, - from: Option>, - to: Option>, - order: Option<#timeline_order>, - namespace: Option, - after: Option, - before: Option, - first: Option, - last: Option, - ) -> #graphql_result<#graphql_connection> { - let connection = #query_impl::activity_timeline( - ctx, - activity_types.map(|xs| xs - .into_iter() - .filter_map(|x| x.into()) - .collect()), - for_agent.map(|xs| xs - .into_iter() - .map(|x| x.into()) - .collect()), - for_entity.map(|xs| xs - .into_iter() - .map(|x| x.into()) - .collect()), - from, - to, - order, - namespace, - after, - before, - first, - last, - ) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))?; - - let mut new_edges = Vec::with_capacity(connection.edges.len()); - - for (i, edge) in connection.edges.into_iter().enumerate() { - let new_node = map_activity_to_domain_type(edge.node); - new_edges.push(connection::Edge::with_additional_fields(i as i32, new_node, #empty_fields)); - } - - let mut new_connection = #graphql_connection::new(connection.has_previous_page, connection.has_next_page); - - new_connection.edges.extend(new_edges); - - Ok(new_connection) - } - - #[doc = #_(#agents_by_type_doc)] - #[allow(clippy::too_many_arguments)] - pub async fn agents_by_type<'a>( - &self, - ctx: &#graphql_context<'a>, - agent_type: AgentType, - namespace: Option<#graphql_id>, - after: Option, - before: Option, - first: Option, - last: Option, - ) -> #graphql_result<#graphql_connection> { - let connection = #query_impl::agents_by_type( - ctx, - agent_type.into(), - namespace, - after, - before, - first, - last, - ) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))?; - - let mut new_edges = Vec::with_capacity(connection.edges.len()); - - for (i, edge) in connection.edges.into_iter().enumerate() { - let new_node = map_agent_to_domain_type(edge.node); - new_edges.push(connection::Edge::with_additional_fields(i as i32, new_node, #empty_fields)); - } - - let mut new_connection = #graphql_connection::new(connection.has_previous_page, connection.has_next_page); - - new_connection.edges.extend(new_edges); - - Ok(new_connection) - } - - #[doc = #_(#activities_by_type_doc)] - #[allow(clippy::too_many_arguments)] - pub async fn activities_by_type<'a>( - &self, - ctx: &#graphql_context<'a>, - activity_type: ActivityType, - namespace: Option<#graphql_id>, - after: Option, - before: Option, - first: Option, - last: Option, - ) -> #graphql_result<#graphql_connection> { - let connection = #query_impl::activities_by_type( - ctx, - activity_type.into(), - namespace, - after, - before, - first, - last, - ) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))?; - - let mut new_edges = Vec::with_capacity(connection.edges.len()); - - for (i, edge) in connection.edges.into_iter().enumerate() { - let new_node = map_activity_to_domain_type(edge.node); - new_edges.push(connection::Edge::with_additional_fields(i as i32, new_node, #empty_fields)); - } - - let mut new_connection = #graphql_connection::new(connection.has_previous_page, connection.has_next_page); - - new_connection.edges.extend(new_edges); - - Ok(new_connection) - } - - #[doc = #_(#entities_by_type_doc)] - #[allow(clippy::too_many_arguments)] - pub async fn entities_by_type<'a>( - &self, - ctx: &#graphql_context<'a>, - entity_type: EntityType, - namespace: Option<#graphql_id>, - after: Option, - before: Option, - first: Option, - last: Option, - ) -> #graphql_result<#graphql_connection> { - let connection = #query_impl::entities_by_type( - ctx, - entity_type.into(), - namespace, - after, - before, - first, - last, - ) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))?; - - let mut new_edges = Vec::with_capacity(connection.edges.len()); - - for (i, edge) in connection.edges.into_iter().enumerate() { - let new_node = map_entity_to_domain_type(edge.node); - new_edges.push(connection::Edge::with_additional_fields(i as i32, new_node, #empty_fields)); - } - - let mut new_connection = #graphql_connection::new(connection.has_previous_page, connection.has_next_page); - - new_connection.edges.extend(new_edges); - - Ok(new_connection) - } - - #[doc = #_(#agent_by_id_doc)] - pub async fn agent_by_id<'a>( - &self, - ctx: &#graphql_context<'a>, - id: #agent_id, - namespace: Option, - ) -> #graphql_result> { - Ok(#query_impl::agent_by_id(ctx, id.into(), namespace) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .map(map_agent_to_domain_type)) - } - - #[doc = #_(#activity_by_id_doc)] - pub async fn activity_by_id<'a>( - &self, - ctx: &#graphql_context<'a>, - id: #activity_id, - namespace: Option, - ) -> #graphql_result> { - Ok(#query_impl::activity_by_id(ctx, id.into(), namespace) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .map(map_activity_to_domain_type)) - } - - #[doc = #_(#entity_by_id_doc)] - pub async fn entity_by_id<'a>( - &self, - ctx: &#graphql_context<'a>, - id: #entity_id, - namespace: Option, - ) -> #graphql_result> { - Ok(#query_impl::entity_by_id(ctx, id.into(), namespace) - .await - .map_err(|e| #async_graphql_error_extensions::extend(&e))? - .map(map_entity_to_domain_type)) - } - } - } + let query_impl = &rust::import("chronicle::api::chronicle_graphql", "query").qualified(); + + let graphql_object = &rust::import("chronicle::async_graphql", "Object"); + let graphql_result = &rust::import("chronicle::async_graphql", "Result"); + let graphql_id = &rust::import("chronicle::async_graphql", "ID"); + let graphql_context = &rust::import("chronicle::async_graphql", "Context"); + let graphql_connection = &rust::import("chronicle::async_graphql::connection", "Connection"); + let async_graphql_error_extensions = + &rust::import("chronicle::async_graphql", "ErrorExtensions").qualified(); + + let agent_id = &rust::import("chronicle::common::prov", "AgentIdOrExternal"); + let entity_id = &rust::import("chronicle::common::prov", "EntityIdOrExternal"); + let activity_id = &rust::import("chronicle::common::prov", "ActivityIdOrExternal"); + let empty_fields = + &rust::import("chronicle::async_graphql::connection", "EmptyFields").qualified(); + + let timeline_order = + &rust::import("chronicle::api::chronicle_graphql", "TimelineOrder").qualified(); + + let activities_by_type_doc = include_str!("../../../../domain_docs/activities_by_type.md"); + let activity_by_id_doc = include_str!("../../../../domain_docs/activity_by_id.md"); + let activity_timeline_doc = include_str!("../../../../domain_docs/activity_timeline.md"); + let agent_by_id_doc = include_str!("../../../../domain_docs/agent_by_id.md"); + let agents_by_type_doc = include_str!("../../../../domain_docs/agents_by_type.md"); + let entities_by_type_doc = include_str!("../../../../domain_docs/entities_by_type.md"); + let entity_by_id_doc = include_str!("../../../../domain_docs/entity_by_id.md"); + + quote! { + #[derive(Copy, Clone)] + pub struct Query; + + #[#graphql_object] + impl Query { + + #[doc = #_(#activity_timeline_doc)] + #[allow(clippy::too_many_arguments)] + pub async fn activity_timeline<'a>( + &self, + ctx: &#graphql_context<'a>, + activity_types: Option>, + for_entity: Option>, + for_agent: Option>, + from: Option>, + to: Option>, + order: Option<#timeline_order>, + namespace: Option, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> #graphql_result<#graphql_connection> { + let connection = #query_impl::activity_timeline( + ctx, + activity_types.map(|xs| xs + .into_iter() + .filter_map(|x| x.into()) + .collect()), + for_agent.map(|xs| xs + .into_iter() + .map(|x| x.into()) + .collect()), + for_entity.map(|xs| xs + .into_iter() + .map(|x| x.into()) + .collect()), + from, + to, + order, + namespace, + after, + before, + first, + last, + ) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))?; + + let mut new_edges = Vec::with_capacity(connection.edges.len()); + + for (i, edge) in connection.edges.into_iter().enumerate() { + let new_node = map_activity_to_domain_type(edge.node); + new_edges.push(connection::Edge::with_additional_fields(i as i32, new_node, #empty_fields)); + } + + let mut new_connection = #graphql_connection::new(connection.has_previous_page, connection.has_next_page); + + new_connection.edges.extend(new_edges); + + Ok(new_connection) + } + + #[doc = #_(#agents_by_type_doc)] + #[allow(clippy::too_many_arguments)] + pub async fn agents_by_type<'a>( + &self, + ctx: &#graphql_context<'a>, + agent_type: AgentType, + namespace: Option<#graphql_id>, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> #graphql_result<#graphql_connection> { + let connection = #query_impl::agents_by_type( + ctx, + agent_type.into(), + namespace, + after, + before, + first, + last, + ) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))?; + + let mut new_edges = Vec::with_capacity(connection.edges.len()); + + for (i, edge) in connection.edges.into_iter().enumerate() { + let new_node = map_agent_to_domain_type(edge.node); + new_edges.push(connection::Edge::with_additional_fields(i as i32, new_node, #empty_fields)); + } + + let mut new_connection = #graphql_connection::new(connection.has_previous_page, connection.has_next_page); + + new_connection.edges.extend(new_edges); + + Ok(new_connection) + } + + #[doc = #_(#activities_by_type_doc)] + #[allow(clippy::too_many_arguments)] + pub async fn activities_by_type<'a>( + &self, + ctx: &#graphql_context<'a>, + activity_type: ActivityType, + namespace: Option<#graphql_id>, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> #graphql_result<#graphql_connection> { + let connection = #query_impl::activities_by_type( + ctx, + activity_type.into(), + namespace, + after, + before, + first, + last, + ) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))?; + + let mut new_edges = Vec::with_capacity(connection.edges.len()); + + for (i, edge) in connection.edges.into_iter().enumerate() { + let new_node = map_activity_to_domain_type(edge.node); + new_edges.push(connection::Edge::with_additional_fields(i as i32, new_node, #empty_fields)); + } + + let mut new_connection = #graphql_connection::new(connection.has_previous_page, connection.has_next_page); + + new_connection.edges.extend(new_edges); + + Ok(new_connection) + } + + #[doc = #_(#entities_by_type_doc)] + #[allow(clippy::too_many_arguments)] + pub async fn entities_by_type<'a>( + &self, + ctx: &#graphql_context<'a>, + entity_type: EntityType, + namespace: Option<#graphql_id>, + after: Option, + before: Option, + first: Option, + last: Option, + ) -> #graphql_result<#graphql_connection> { + let connection = #query_impl::entities_by_type( + ctx, + entity_type.into(), + namespace, + after, + before, + first, + last, + ) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))?; + + let mut new_edges = Vec::with_capacity(connection.edges.len()); + + for (i, edge) in connection.edges.into_iter().enumerate() { + let new_node = map_entity_to_domain_type(edge.node); + new_edges.push(connection::Edge::with_additional_fields(i as i32, new_node, #empty_fields)); + } + + let mut new_connection = #graphql_connection::new(connection.has_previous_page, connection.has_next_page); + + new_connection.edges.extend(new_edges); + + Ok(new_connection) + } + + #[doc = #_(#agent_by_id_doc)] + pub async fn agent_by_id<'a>( + &self, + ctx: &#graphql_context<'a>, + id: #agent_id, + namespace: Option, + ) -> #graphql_result> { + Ok(#query_impl::agent_by_id(ctx, id.into(), namespace) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .map(map_agent_to_domain_type)) + } + + #[doc = #_(#activity_by_id_doc)] + pub async fn activity_by_id<'a>( + &self, + ctx: &#graphql_context<'a>, + id: #activity_id, + namespace: Option, + ) -> #graphql_result> { + Ok(#query_impl::activity_by_id(ctx, id.into(), namespace) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .map(map_activity_to_domain_type)) + } + + #[doc = #_(#entity_by_id_doc)] + pub async fn entity_by_id<'a>( + &self, + ctx: &#graphql_context<'a>, + id: #entity_id, + namespace: Option, + ) -> #graphql_result> { + Ok(#query_impl::entity_by_id(ctx, id.into(), namespace) + .await + .map_err(|e| #async_graphql_error_extensions::extend(&e))? + .map(map_entity_to_domain_type)) + } + } + } } fn gen_mutation(domain: &ChronicleDomainDef) -> rust::Tokens { - let graphql_object = &rust::import("chronicle::async_graphql", "Object"); - - let graphql_result = &rust::import("chronicle::async_graphql", "Result"); - let graphql_context = &rust::import("chronicle::async_graphql", "Context"); - let async_graphql_error_extensions = - &rust::import("chronicle::async_graphql", "ErrorExtensions").qualified(); - - let submission = &rust::import("chronicle::api::chronicle_graphql", "Submission"); - let impls = &rust::import("chronicle::api::chronicle_graphql", "mutation"); - - let entity_id = &rust::import("chronicle::common::prov", "EntityIdOrExternal"); - let agent_id = &rust::import("chronicle::common::prov", "AgentIdOrExternal"); - let activity_id = &rust::import("chronicle::common::prov", "ActivityIdOrExternal"); - let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); - - let abstract_attributes = - &rust::import("chronicle::common::attributes", "Attributes").qualified(); - - let acted_on_behalf_of_doc = include_str!("../../../../domain_docs/acted_on_behalf_of.md"); - let define_doc = include_str!("../../../../domain_docs/define.md"); - let end_doc = include_str!("../../../../domain_docs/end_activity.md"); - let had_primary_source_doc = include_str!("../../../../domain_docs/had_primary_source.md"); - let instant_activity_doc = include_str!("../../../../domain_docs/instant_activity.md"); - let prov_activity_doc = include_str!("../../../../domain_docs/prov_activity.md"); - let prov_agent_doc = include_str!("../../../../domain_docs/prov_agent.md"); - let prov_entity_doc = include_str!("../../../../domain_docs/prov_entity.md"); - let start_doc = include_str!("../../../../domain_docs/start_activity.md"); - let used_doc = include_str!("../../../../domain_docs/used.md"); - let was_associated_with_doc = include_str!("../../../../domain_docs/was_associated_with.md"); - let was_attributed_to_doc = include_str!("../../../../domain_docs/was_attributed_to.md"); - let was_derived_from_doc = include_str!("../../../../domain_docs/was_derived_from.md"); - let was_generated_by_doc = include_str!("../../../../domain_docs/was_generated_by.md"); - let was_informed_by_doc = include_str!("../../../../domain_docs/was_informed_by.md"); - let was_quoted_from_doc = include_str!("../../../../domain_docs/was_quoted_from.md"); - let was_revision_of_doc = include_str!("../../../../domain_docs/was_revision_of.md"); - - quote! { - #[derive(Copy, Clone)] - pub struct Mutation; - - #[#graphql_object] - impl Mutation { - #[doc = #_(#define_doc)] - #[doc = ""] - #[doc = #_(#prov_agent_doc)] - pub async fn define_agent<'a>( - &self, - ctx: &#graphql_context<'a>, - external_id: String, - namespace: Option, - attributes: ProvAgentAttributes, - ) -> async_graphql::#graphql_result<#submission> { - #impls::agent(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #(for agent in domain.agents.iter() => - #[doc = #_(#define_doc)] - #(if agent.doc.is_some() { - #[doc = ""] - #[doc = #_(#(agent.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #(if agent.attributes.is_empty() { - #[graphql(name = #_(#(agent.as_method_name())))] - pub async fn #(&agent.as_property())<'a>( - &self, - ctx: &#graphql_context<'a>, - external_id: String, - namespace: Option, - ) -> async_graphql::#graphql_result<#submission> { - #impls::agent(ctx, external_id, namespace, - #abstract_attributes::type_only(Some( - #domain_type_id::from_external_id(#_(#(agent.as_type_name()))) - )) - ).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - } else { - #[graphql(name = #_(#(agent.as_method_name())))] - pub async fn #(&agent.as_property())<'a>( - &self, - ctx: &#graphql_context<'a>, - external_id: String, - namespace: Option, - attributes: #(agent.attributes_type_name_preserve_inflection()), - ) -> async_graphql::#graphql_result<#submission> { - #impls::agent(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - } - ) - ) - - #[doc = #_(#define_doc)] - #[doc = ""] - #[doc = #_(#prov_activity_doc)] - pub async fn define_activity<'a>( - &self, - ctx: &#graphql_context<'a>, - external_id: String, - namespace: Option, - attributes: ProvActivityAttributes, - ) -> async_graphql::#graphql_result<#submission> { - #impls::activity(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #(for activity in domain.activities.iter() => - #[doc = #_(#define_doc)] - #(if activity.doc.is_some() { - #[doc = ""] - #[doc = #_(#(activity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #(if activity.attributes.is_empty() { - #[graphql(name = #_(#(activity.as_method_name())))] - pub async fn #(&activity.as_property())<'a>( - &self, - ctx: &#graphql_context<'a>, - external_id: String, - namespace: Option, - ) -> async_graphql::#graphql_result<#submission> { - #impls::activity(ctx, external_id, namespace, - #abstract_attributes::type_only(Some( - #domain_type_id::from_external_id(#_(#(activity.as_type_name()))) - )) - ).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - } else { - #[graphql(name = #_(#(activity.as_method_name())))] - pub async fn #(&activity.as_property())<'a>( - &self, - ctx: &#graphql_context<'a>, - external_id: String, - namespace: Option, - attributes: #(activity.attributes_type_name_preserve_inflection()), - ) -> async_graphql::#graphql_result<#submission> { - #impls::activity(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - } - ) - ) - - #[doc = #_(#define_doc)] - #[doc = ""] - #[doc = #_(#prov_entity_doc)] - pub async fn define_entity<'a>( - &self, - ctx: &#graphql_context<'a>, - external_id: String, - namespace: Option, - attributes: ProvEntityAttributes, - ) -> async_graphql::#graphql_result<#submission> { - #impls::entity(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #(for entity in domain.entities.iter() => - #[doc = #_(#define_doc)] - #(if entity.doc.is_some() { - #[doc = ""] - #[doc = #_(#(entity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] - }) - #(if entity.attributes.is_empty() { - #[graphql(name = #_(#(entity.as_method_name())))] - pub async fn #(&entity.as_property())<'a>( - &self, - ctx: &#graphql_context<'a>, - external_id: String, - namespace: Option, - ) -> async_graphql::#graphql_result<#submission> { - #impls::entity(ctx, external_id, namespace, - #abstract_attributes::type_only(Some( - #domain_type_id::from_external_id(#_(#(entity.as_type_name()))) - )) - ).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - } else { - #[graphql(name = #_(#(entity.as_method_name())))] - pub async fn #(&entity.as_property())<'a>( - &self, - ctx: &#graphql_context<'a>, - external_id: String, - namespace: Option, - attributes: #(entity.attributes_type_name_preserve_inflection()), - ) -> async_graphql::#graphql_result<#submission> { - #impls::entity(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - } - ) - ) - - #[doc = #_(#acted_on_behalf_of_doc)] - pub async fn acted_on_behalf_of<'a>( - &self, - ctx: &#graphql_context<'a>, - namespace: Option, - responsible: #agent_id, - delegate: #agent_id, - activity: Option<#activity_id>, - role: RoleType, - ) -> async_graphql::#graphql_result<#submission> { - let activity = activity.map(|activity| activity.into()); - #impls::acted_on_behalf_of(ctx, namespace, responsible.into(), delegate.into(), activity, role.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#was_derived_from_doc)] - pub async fn was_derived_from<'a>( - &self, - ctx: &#graphql_context<'a>, - namespace: Option, - generated_entity: #entity_id, - used_entity: #entity_id, - ) -> async_graphql::#graphql_result<#submission> { - #impls::was_derived_from(ctx, namespace, generated_entity.into(), used_entity.into()) - .await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#was_revision_of_doc)] - pub async fn was_revision_of<'a>( - &self, - ctx: &#graphql_context<'a>, - namespace: Option, - generated_entity: #entity_id, - used_entity: #entity_id, - ) -> async_graphql::#graphql_result<#submission> { - #impls::was_revision_of(ctx, namespace, generated_entity.into(), used_entity.into()) - .await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#had_primary_source_doc)] - pub async fn had_primary_source<'a>( - &self, - ctx: &#graphql_context<'a>, - namespace: Option, - generated_entity: #entity_id, - used_entity: #entity_id, - ) -> async_graphql::#graphql_result<#submission> { - #impls::had_primary_source( - ctx, - namespace, - generated_entity.into(), - used_entity.into(), - ) - .await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#was_quoted_from_doc)] - pub async fn was_quoted_from<'a>( - &self, - ctx: &#graphql_context<'a>, - namespace: Option, - generated_entity: #entity_id, - used_entity: #entity_id, - ) -> async_graphql::#graphql_result<#submission> { - #impls::was_quoted_from(ctx, namespace, generated_entity.into(), used_entity.into()) - .await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#instant_activity_doc)] - pub async fn instant_activity<'a>( - &self, - ctx: &#graphql_context<'a>, - id: #activity_id, - namespace: Option, - agent: Option<#agent_id>, - time: Option>, - ) -> async_graphql::#graphql_result<#submission> { - let agent = agent.map(|agent| agent.into()); - #impls::instant_activity(ctx, id.into(), namespace, agent, time).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#start_doc)] - pub async fn start_activity<'a>( - &self, - ctx: &#graphql_context<'a>, - id: #activity_id, - namespace: Option, - agent: Option<#agent_id>, - time: Option>, - ) -> async_graphql::#graphql_result<#submission> { - let agent = agent.map(|agent| agent.into()); - #impls::start_activity(ctx, id.into(), namespace, agent, time).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#end_doc)] - pub async fn end_activity<'a>( - &self, - ctx: &#graphql_context<'a>, - id: #activity_id, - namespace: Option, - agent: Option<#agent_id>, - time: Option>, - ) -> async_graphql::#graphql_result<#submission> { - let agent = agent.map(|agent| agent.into()); - #impls::end_activity(ctx, id.into(), namespace, agent, time).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#was_associated_with_doc)] - pub async fn was_associated_with<'a>( - &self, - ctx: &#graphql_context<'a>, - namespace: Option, - responsible: #agent_id, - activity: #activity_id, - role: RoleType - ) -> async_graphql::#graphql_result<#submission> { - #impls::was_associated_with(ctx, namespace, responsible.into(), activity.into(), role.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#was_attributed_to_doc)] - pub async fn was_attributed_to<'a>( - &self, - ctx: &#graphql_context<'a>, - namespace: Option, - responsible: #agent_id, - entity: #entity_id, - role: RoleType - ) -> async_graphql::#graphql_result<#submission> { - #impls::was_attributed_to(ctx, namespace, responsible.into(), entity.into(), role.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#used_doc)] - pub async fn used<'a>( - &self, - ctx: &#graphql_context<'a>, - activity: #activity_id, - id: #entity_id, - namespace: Option, - ) -> async_graphql::#graphql_result<#submission> { - #impls::used(ctx, activity.into(), id.into(), namespace).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#was_informed_by_doc)] - pub async fn was_informed_by<'a>( - &self, - ctx: &#graphql_context<'a>, - activity: #activity_id, - informing_activity: #activity_id, - namespace: Option, - ) -> async_graphql::#graphql_result<#submission> { - #impls::was_informed_by(ctx, activity.into(), informing_activity.into(), namespace).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - - #[doc = #_(#was_generated_by_doc)] - pub async fn was_generated_by<'a>( - &self, - ctx: &#graphql_context<'a>, - activity: #activity_id, - id: #entity_id, - namespace: Option, - ) -> async_graphql::#graphql_result<#submission> { - #impls::was_generated_by(ctx, activity.into(), id.into(), namespace).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) - } - } - } + let graphql_object = &rust::import("chronicle::async_graphql", "Object"); + + let graphql_result = &rust::import("chronicle::async_graphql", "Result"); + let graphql_context = &rust::import("chronicle::async_graphql", "Context"); + let async_graphql_error_extensions = + &rust::import("chronicle::async_graphql", "ErrorExtensions").qualified(); + + let submission = &rust::import("chronicle::api::chronicle_graphql", "Submission"); + let impls = &rust::import("chronicle::api::chronicle_graphql", "mutation"); + + let entity_id = &rust::import("chronicle::common::prov", "EntityIdOrExternal"); + let agent_id = &rust::import("chronicle::common::prov", "AgentIdOrExternal"); + let activity_id = &rust::import("chronicle::common::prov", "ActivityIdOrExternal"); + let domain_type_id = &rust::import("chronicle::common::prov", "DomaintypeId"); + + let abstract_attributes = + &rust::import("chronicle::common::attributes", "Attributes").qualified(); + + let acted_on_behalf_of_doc = include_str!("../../../../domain_docs/acted_on_behalf_of.md"); + let define_doc = include_str!("../../../../domain_docs/define.md"); + let end_doc = include_str!("../../../../domain_docs/end_activity.md"); + let had_primary_source_doc = include_str!("../../../../domain_docs/had_primary_source.md"); + let instant_activity_doc = include_str!("../../../../domain_docs/instant_activity.md"); + let prov_activity_doc = include_str!("../../../../domain_docs/prov_activity.md"); + let prov_agent_doc = include_str!("../../../../domain_docs/prov_agent.md"); + let prov_entity_doc = include_str!("../../../../domain_docs/prov_entity.md"); + let start_doc = include_str!("../../../../domain_docs/start_activity.md"); + let used_doc = include_str!("../../../../domain_docs/used.md"); + let was_associated_with_doc = include_str!("../../../../domain_docs/was_associated_with.md"); + let was_attributed_to_doc = include_str!("../../../../domain_docs/was_attributed_to.md"); + let was_derived_from_doc = include_str!("../../../../domain_docs/was_derived_from.md"); + let was_generated_by_doc = include_str!("../../../../domain_docs/was_generated_by.md"); + let was_informed_by_doc = include_str!("../../../../domain_docs/was_informed_by.md"); + let was_quoted_from_doc = include_str!("../../../../domain_docs/was_quoted_from.md"); + let was_revision_of_doc = include_str!("../../../../domain_docs/was_revision_of.md"); + + quote! { + #[derive(Copy, Clone)] + pub struct Mutation; + + #[#graphql_object] + impl Mutation { + #[doc = #_(#define_doc)] + #[doc = ""] + #[doc = #_(#prov_agent_doc)] + pub async fn define_agent<'a>( + &self, + ctx: &#graphql_context<'a>, + external_id: String, + namespace: Option, + attributes: ProvAgentAttributes, + ) -> async_graphql::#graphql_result<#submission> { + #impls::agent(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #(for agent in domain.agents.iter() => + #[doc = #_(#define_doc)] + #(if agent.doc.is_some() { + #[doc = ""] + #[doc = #_(#(agent.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #(if agent.attributes.is_empty() { + #[graphql(name = #_(#(agent.as_method_name())))] + pub async fn #(&agent.as_property())<'a>( + &self, + ctx: &#graphql_context<'a>, + external_id: String, + namespace: Option, + ) -> async_graphql::#graphql_result<#submission> { + #impls::agent(ctx, external_id, namespace, + #abstract_attributes::type_only(Some( + #domain_type_id::from_external_id(#_(#(agent.as_type_name()))) + )) + ).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + } else { + #[graphql(name = #_(#(agent.as_method_name())))] + pub async fn #(&agent.as_property())<'a>( + &self, + ctx: &#graphql_context<'a>, + external_id: String, + namespace: Option, + attributes: #(agent.attributes_type_name_preserve_inflection()), + ) -> async_graphql::#graphql_result<#submission> { + #impls::agent(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + } + ) + ) + + #[doc = #_(#define_doc)] + #[doc = ""] + #[doc = #_(#prov_activity_doc)] + pub async fn define_activity<'a>( + &self, + ctx: &#graphql_context<'a>, + external_id: String, + namespace: Option, + attributes: ProvActivityAttributes, + ) -> async_graphql::#graphql_result<#submission> { + #impls::activity(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #(for activity in domain.activities.iter() => + #[doc = #_(#define_doc)] + #(if activity.doc.is_some() { + #[doc = ""] + #[doc = #_(#(activity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #(if activity.attributes.is_empty() { + #[graphql(name = #_(#(activity.as_method_name())))] + pub async fn #(&activity.as_property())<'a>( + &self, + ctx: &#graphql_context<'a>, + external_id: String, + namespace: Option, + ) -> async_graphql::#graphql_result<#submission> { + #impls::activity(ctx, external_id, namespace, + #abstract_attributes::type_only(Some( + #domain_type_id::from_external_id(#_(#(activity.as_type_name()))) + )) + ).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + } else { + #[graphql(name = #_(#(activity.as_method_name())))] + pub async fn #(&activity.as_property())<'a>( + &self, + ctx: &#graphql_context<'a>, + external_id: String, + namespace: Option, + attributes: #(activity.attributes_type_name_preserve_inflection()), + ) -> async_graphql::#graphql_result<#submission> { + #impls::activity(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + } + ) + ) + + #[doc = #_(#define_doc)] + #[doc = ""] + #[doc = #_(#prov_entity_doc)] + pub async fn define_entity<'a>( + &self, + ctx: &#graphql_context<'a>, + external_id: String, + namespace: Option, + attributes: ProvEntityAttributes, + ) -> async_graphql::#graphql_result<#submission> { + #impls::entity(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #(for entity in domain.entities.iter() => + #[doc = #_(#define_doc)] + #(if entity.doc.is_some() { + #[doc = ""] + #[doc = #_(#(entity.doc.as_ref().map(|s| s.to_owned()).unwrap_or_default()))] + }) + #(if entity.attributes.is_empty() { + #[graphql(name = #_(#(entity.as_method_name())))] + pub async fn #(&entity.as_property())<'a>( + &self, + ctx: &#graphql_context<'a>, + external_id: String, + namespace: Option, + ) -> async_graphql::#graphql_result<#submission> { + #impls::entity(ctx, external_id, namespace, + #abstract_attributes::type_only(Some( + #domain_type_id::from_external_id(#_(#(entity.as_type_name()))) + )) + ).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + } else { + #[graphql(name = #_(#(entity.as_method_name())))] + pub async fn #(&entity.as_property())<'a>( + &self, + ctx: &#graphql_context<'a>, + external_id: String, + namespace: Option, + attributes: #(entity.attributes_type_name_preserve_inflection()), + ) -> async_graphql::#graphql_result<#submission> { + #impls::entity(ctx, external_id, namespace, attributes.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + } + ) + ) + + #[doc = #_(#acted_on_behalf_of_doc)] + pub async fn acted_on_behalf_of<'a>( + &self, + ctx: &#graphql_context<'a>, + namespace: Option, + responsible: #agent_id, + delegate: #agent_id, + activity: Option<#activity_id>, + role: RoleType, + ) -> async_graphql::#graphql_result<#submission> { + let activity = activity.map(|activity| activity.into()); + #impls::acted_on_behalf_of(ctx, namespace, responsible.into(), delegate.into(), activity, role.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#was_derived_from_doc)] + pub async fn was_derived_from<'a>( + &self, + ctx: &#graphql_context<'a>, + namespace: Option, + generated_entity: #entity_id, + used_entity: #entity_id, + ) -> async_graphql::#graphql_result<#submission> { + #impls::was_derived_from(ctx, namespace, generated_entity.into(), used_entity.into()) + .await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#was_revision_of_doc)] + pub async fn was_revision_of<'a>( + &self, + ctx: &#graphql_context<'a>, + namespace: Option, + generated_entity: #entity_id, + used_entity: #entity_id, + ) -> async_graphql::#graphql_result<#submission> { + #impls::was_revision_of(ctx, namespace, generated_entity.into(), used_entity.into()) + .await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#had_primary_source_doc)] + pub async fn had_primary_source<'a>( + &self, + ctx: &#graphql_context<'a>, + namespace: Option, + generated_entity: #entity_id, + used_entity: #entity_id, + ) -> async_graphql::#graphql_result<#submission> { + #impls::had_primary_source( + ctx, + namespace, + generated_entity.into(), + used_entity.into(), + ) + .await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#was_quoted_from_doc)] + pub async fn was_quoted_from<'a>( + &self, + ctx: &#graphql_context<'a>, + namespace: Option, + generated_entity: #entity_id, + used_entity: #entity_id, + ) -> async_graphql::#graphql_result<#submission> { + #impls::was_quoted_from(ctx, namespace, generated_entity.into(), used_entity.into()) + .await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#instant_activity_doc)] + pub async fn instant_activity<'a>( + &self, + ctx: &#graphql_context<'a>, + id: #activity_id, + namespace: Option, + agent: Option<#agent_id>, + time: Option>, + ) -> async_graphql::#graphql_result<#submission> { + let agent = agent.map(|agent| agent.into()); + #impls::instant_activity(ctx, id.into(), namespace, agent, time).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#start_doc)] + pub async fn start_activity<'a>( + &self, + ctx: &#graphql_context<'a>, + id: #activity_id, + namespace: Option, + agent: Option<#agent_id>, + time: Option>, + ) -> async_graphql::#graphql_result<#submission> { + let agent = agent.map(|agent| agent.into()); + #impls::start_activity(ctx, id.into(), namespace, agent, time).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#end_doc)] + pub async fn end_activity<'a>( + &self, + ctx: &#graphql_context<'a>, + id: #activity_id, + namespace: Option, + agent: Option<#agent_id>, + time: Option>, + ) -> async_graphql::#graphql_result<#submission> { + let agent = agent.map(|agent| agent.into()); + #impls::end_activity(ctx, id.into(), namespace, agent, time).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#was_associated_with_doc)] + pub async fn was_associated_with<'a>( + &self, + ctx: &#graphql_context<'a>, + namespace: Option, + responsible: #agent_id, + activity: #activity_id, + role: RoleType + ) -> async_graphql::#graphql_result<#submission> { + #impls::was_associated_with(ctx, namespace, responsible.into(), activity.into(), role.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#was_attributed_to_doc)] + pub async fn was_attributed_to<'a>( + &self, + ctx: &#graphql_context<'a>, + namespace: Option, + responsible: #agent_id, + entity: #entity_id, + role: RoleType + ) -> async_graphql::#graphql_result<#submission> { + #impls::was_attributed_to(ctx, namespace, responsible.into(), entity.into(), role.into()).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#used_doc)] + pub async fn used<'a>( + &self, + ctx: &#graphql_context<'a>, + activity: #activity_id, + id: #entity_id, + namespace: Option, + ) -> async_graphql::#graphql_result<#submission> { + #impls::used(ctx, activity.into(), id.into(), namespace).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#was_informed_by_doc)] + pub async fn was_informed_by<'a>( + &self, + ctx: &#graphql_context<'a>, + activity: #activity_id, + informing_activity: #activity_id, + namespace: Option, + ) -> async_graphql::#graphql_result<#submission> { + #impls::was_informed_by(ctx, activity.into(), informing_activity.into(), namespace).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + + #[doc = #_(#was_generated_by_doc)] + pub async fn was_generated_by<'a>( + &self, + ctx: &#graphql_context<'a>, + activity: #activity_id, + id: #entity_id, + namespace: Option, + ) -> async_graphql::#graphql_result<#submission> { + #impls::was_generated_by(ctx, activity.into(), id.into(), namespace).await.map_err(|e| #async_graphql_error_extensions::extend(&e)) + } + } + } } fn gen_graphql_type(domain: &ChronicleDomainDef) -> rust::Tokens { - let prov_agent = AgentDef { - external_id: "ProvAgent".to_owned(), - doc: Some(include_str!("../../../../domain_docs/prov_agent.md").to_string()), - attributes: vec![], - }; - let prov_activity = ActivityDef { - external_id: "ProvActivity".to_owned(), - doc: Some(include_str!("../../../../domain_docs/prov_activity.md").to_string()), - attributes: vec![], - }; - let prov_entity = EntityDef { - external_id: "ProvEntity".to_owned(), - doc: Some(include_str!("../../../../domain_docs/prov_entity.md").to_string()), - attributes: vec![], - }; - - let chronicledomaindef = &rust::import("chronicle::codegen", "ChronicleDomainDef"); - let tokio = &rust::import("chronicle", "tokio"); - - let bootstrap = rust::import("chronicle::bootstrap", "bootstrap"); - let chronicle_graphql = rust::import("chronicle::api::chronicle_graphql", "ChronicleGraphQl"); - - quote! { - #(gen_attribute_scalars(&domain.attributes)) - #(gen_type_enums(domain)) - #(gen_association_and_attribution_unions()) - #(gen_abstract_prov_attributes()) - #(for agent in domain.agents.iter() => #(gen_attribute_definition(agent, &agent.attributes))) - #(for activity in domain.activities.iter() => #(gen_attribute_definition(activity, &activity.attributes))) - #(for entity in domain.entities.iter() => #(gen_attribute_definition(entity, &entity.attributes))) - #(gen_agent_union(&domain.agents)) - #(gen_entity_union(&domain.entities)) - #(gen_activity_union(&domain.activities)) - #(gen_mappers(domain)) - #(gen_agent_definition(&prov_agent)) - #(gen_activity_definition(&prov_activity)) - #(gen_entity_definition(&prov_entity)) - #(for agent in domain.agents.iter() => #(gen_agent_definition(agent))) - #(for activity in domain.activities.iter() => #(gen_activity_definition(activity))) - #(for entity in domain.entities.iter() => #(gen_entity_definition(entity))) - #(gen_query()) - #(gen_mutation(domain)) - - #[#tokio::main] - pub async fn main() { - let model = #chronicledomaindef::from_input_string(#_(#(&domain.to_json_string().unwrap()))).unwrap(); - - #bootstrap(model, #chronicle_graphql::new(Query, Mutation)).await - } - - } + let prov_agent = AgentDef { + external_id: "ProvAgent".to_owned(), + doc: Some(include_str!("../../../../domain_docs/prov_agent.md").to_string()), + attributes: vec![], + }; + let prov_activity = ActivityDef { + external_id: "ProvActivity".to_owned(), + doc: Some(include_str!("../../../../domain_docs/prov_activity.md").to_string()), + attributes: vec![], + }; + let prov_entity = EntityDef { + external_id: "ProvEntity".to_owned(), + doc: Some(include_str!("../../../../domain_docs/prov_entity.md").to_string()), + attributes: vec![], + }; + + let chronicledomaindef = &rust::import("chronicle::codegen", "ChronicleDomainDef"); + let tokio = &rust::import("chronicle", "tokio"); + + let bootstrap = rust::import("chronicle::bootstrap", "bootstrap"); + let chronicle_graphql = rust::import("chronicle::api::chronicle_graphql", "ChronicleGraphQl"); + + quote! { + #(gen_attribute_scalars(&domain.attributes)) + #(gen_type_enums(domain)) + #(gen_association_and_attribution_unions()) + #(gen_abstract_prov_attributes()) + #(for agent in domain.agents.iter() => #(gen_attribute_definition(agent, &agent.attributes))) + #(for activity in domain.activities.iter() => #(gen_attribute_definition(activity, &activity.attributes))) + #(for entity in domain.entities.iter() => #(gen_attribute_definition(entity, &entity.attributes))) + #(gen_agent_union(&domain.agents)) + #(gen_entity_union(&domain.entities)) + #(gen_activity_union(&domain.activities)) + #(gen_mappers(domain)) + #(gen_agent_definition(&prov_agent)) + #(gen_activity_definition(&prov_activity)) + #(gen_entity_definition(&prov_entity)) + #(for agent in domain.agents.iter() => #(gen_agent_definition(agent))) + #(for activity in domain.activities.iter() => #(gen_activity_definition(activity))) + #(for entity in domain.entities.iter() => #(gen_entity_definition(entity))) + #(gen_query()) + #(gen_mutation(domain)) + + #[#tokio::main] + pub async fn main() { + let model = #chronicledomaindef::from_input_string(#_(#(&domain.to_json_string().unwrap()))).unwrap(); + + #bootstrap(model, #chronicle_graphql::new(Query, Mutation)).await + } + + } } pub fn generate_chronicle_domain_schema(domain: ChronicleDomainDef, path: impl AsRef) { - let tokens = gen_graphql_type(&domain); + let tokens = gen_graphql_type(&domain); - path.as_ref().parent().map(std::fs::create_dir_all); - let mut f = std::fs::File::create(path).unwrap(); - f.write_all(tokens.to_file_string().unwrap().as_bytes()) - .unwrap(); + path.as_ref().parent().map(std::fs::create_dir_all); + let mut f = std::fs::File::create(path).unwrap(); + f.write_all(tokens.to_file_string().unwrap().as_bytes()).unwrap(); - f.flush().unwrap(); + f.flush().unwrap(); } diff --git a/crates/chronicle/src/codegen/model.rs b/crates/chronicle/src/codegen/model.rs index 46e9780a4..7f57f6aac 100644 --- a/crates/chronicle/src/codegen/model.rs +++ b/crates/chronicle/src/codegen/model.rs @@ -1,557 +1,501 @@ use std::{collections::BTreeMap, path::Path, str::FromStr}; use inflector::cases::{ - camelcase::to_camel_case, kebabcase::to_kebab_case, pascalcase::to_pascal_case, - snakecase::to_snake_case, + camelcase::to_camel_case, kebabcase::to_kebab_case, pascalcase::to_pascal_case, + snakecase::to_snake_case, }; use serde::{Deserialize, Serialize}; use thiserror::Error; #[derive(Debug, Error)] pub enum ModelError { - #[error("Attribute not defined argument: {attr}")] - AttributeNotDefined { attr: String }, + #[error("Attribute not defined argument: {attr}")] + AttributeNotDefined { attr: String }, - #[error("Model file not readable: {0}")] - ModelFileNotReadable(#[from] std::io::Error), + #[error("Model file not readable: {0}")] + ModelFileNotReadable(#[from] std::io::Error), - #[error("Model file invalid JSON: {0}")] - ModelFileInvalidJson(#[from] serde_json::Error), + #[error("Model file invalid JSON: {0}")] + ModelFileInvalidJson(#[from] serde_json::Error), - #[error("Model file invalid YAML: {0}")] - ModelFileInvalidYaml(#[from] serde_yaml::Error), + #[error("Model file invalid YAML: {0}")] + ModelFileInvalidYaml(#[from] serde_yaml::Error), } #[derive(Deserialize, Serialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum PrimitiveType { - String, - Bool, - Int, - JSON, + String, + Bool, + Int, + JSON, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AttributeDef { - typ: String, - pub(crate) doc: Option, - pub(crate) primitive_type: PrimitiveType, + typ: String, + pub(crate) doc: Option, + pub(crate) primitive_type: PrimitiveType, } impl TypeName for AttributeDef { - fn as_type_name(&self) -> String { - to_pascal_case(&self.typ) - } - - fn preserve_inflection(&self) -> String { - match ( - self.typ.chars().next(), - self.typ.chars().nth(1), - &self.typ[1..], - ) { - (_, Some(c), _) if c.is_uppercase() => format!("{}Attribute", self.typ), - (Some(first), _, body) => format!("{}{}Attribute", first.to_lowercase(), body), - _ => format!("{}Attribute", self.typ), - } - } + fn as_type_name(&self) -> String { + to_pascal_case(&self.typ) + } + + fn preserve_inflection(&self) -> String { + match (self.typ.chars().next(), self.typ.chars().nth(1), &self.typ[1..]) { + (_, Some(c), _) if c.is_uppercase() => format!("{}Attribute", self.typ), + (Some(first), _, body) => format!("{}{}Attribute", first.to_lowercase(), body), + _ => format!("{}Attribute", self.typ), + } + } } impl AttributeDef { - pub fn as_scalar_type(&self) -> String { - match ( - self.typ.chars().next(), - self.typ.chars().nth(1), - &self.typ[1..], - ) { - (_, Some(c), _) if c.is_uppercase() => format!("{}Attribute", self.typ), - (Some(first), _, body) => format!("{}{}Attribute", first.to_uppercase(), body), - _ => format!("{}Attribute", self.as_type_name()), - } - } - - pub(crate) fn as_property(&self) -> String { - to_snake_case(&format!("{}Attribute", self.typ)) - } - - pub(crate) fn from_attribute_file_input(external_id: String, attr: AttributeFileInput) -> Self { - AttributeDef { - typ: external_id, - doc: attr.doc, - primitive_type: attr.typ, - } - } + pub fn as_scalar_type(&self) -> String { + match (self.typ.chars().next(), self.typ.chars().nth(1), &self.typ[1..]) { + (_, Some(c), _) if c.is_uppercase() => format!("{}Attribute", self.typ), + (Some(first), _, body) => format!("{}{}Attribute", first.to_uppercase(), body), + _ => format!("{}Attribute", self.as_type_name()), + } + } + + pub(crate) fn as_property(&self) -> String { + to_snake_case(&format!("{}Attribute", self.typ)) + } + + pub(crate) fn from_attribute_file_input(external_id: String, attr: AttributeFileInput) -> Self { + AttributeDef { typ: external_id, doc: attr.doc, primitive_type: attr.typ } + } } /// A external_id formatted for CLI use - kebab-case, singular, lowercase pub trait CliName { - fn as_cli_name(&self) -> String; + fn as_cli_name(&self) -> String; } /// A correctly cased and singularized external_id for the type pub trait TypeName { - fn as_type_name(&self) -> String; - fn preserve_inflection(&self) -> String; + fn as_type_name(&self) -> String; + fn preserve_inflection(&self) -> String; - fn as_method_name(&self) -> String { - format!("define{}", self.as_type_name()) - } + fn as_method_name(&self) -> String { + format!("define{}", self.as_type_name()) + } } /// Entities, Activities and Agents have a specific set of attributes. pub trait AttributesTypeName { - fn attributes_type_name(&self) -> String; - fn attributes_type_name_preserve_inflection(&self) -> String; + fn attributes_type_name(&self) -> String; + fn attributes_type_name_preserve_inflection(&self) -> String; } pub trait Property { - fn as_property(&self) -> String; + fn as_property(&self) -> String; } impl AttributesTypeName for T where - T: TypeName, + T: TypeName, { - fn attributes_type_name(&self) -> String { - to_pascal_case(&format!("{}Attributes", self.as_type_name())) - } + fn attributes_type_name(&self) -> String { + to_pascal_case(&format!("{}Attributes", self.as_type_name())) + } - fn attributes_type_name_preserve_inflection(&self) -> String { - format!("{}Attributes", self.as_type_name()) - } + fn attributes_type_name_preserve_inflection(&self) -> String { + format!("{}Attributes", self.as_type_name()) + } } impl CliName for T where - T: TypeName, + T: TypeName, { - fn as_cli_name(&self) -> String { - to_kebab_case(&self.as_type_name()) - } + fn as_cli_name(&self) -> String { + to_kebab_case(&self.as_type_name()) + } } impl Property for T where - T: TypeName, + T: TypeName, { - fn as_property(&self) -> String { - to_snake_case(&self.as_type_name()) - } + fn as_property(&self) -> String { + to_snake_case(&self.as_type_name()) + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AgentDef { - pub(crate) external_id: String, - pub(crate) doc: Option, - pub(crate) attributes: Vec, + pub(crate) external_id: String, + pub(crate) doc: Option, + pub(crate) attributes: Vec, } impl TypeName for &AgentDef { - fn as_type_name(&self) -> String { - type_name_for_kind("Agent", &self.external_id) - } + fn as_type_name(&self) -> String { + type_name_for_kind("Agent", &self.external_id) + } - fn preserve_inflection(&self) -> String { - preserve_inflection_for_kind("Agent", &self.external_id) - } + fn preserve_inflection(&self) -> String { + preserve_inflection_for_kind("Agent", &self.external_id) + } } impl AgentDef { - pub fn new( - external_id: impl AsRef, - doc: Option, - attributes: Vec, - ) -> Self { - Self { - external_id: external_id.as_ref().to_string(), - doc, - attributes, - } - } - - pub fn from_input<'a>( - external_id: String, - doc: Option, - attributes: &BTreeMap, - attribute_references: impl Iterator, - ) -> Result { - Ok(Self { - external_id, - doc, - attributes: attribute_references - .map(|x| { - attributes - .get(&*x.0) - .ok_or_else(|| ModelError::AttributeNotDefined { - attr: x.0.to_owned(), - }) - .map(|attr| AttributeDef { - typ: x.0.to_owned(), - doc: attr.doc.to_owned(), - primitive_type: attr.typ, - }) - }) - .collect::, _>>()?, - }) - } + pub fn new( + external_id: impl AsRef, + doc: Option, + attributes: Vec, + ) -> Self { + Self { external_id: external_id.as_ref().to_string(), doc, attributes } + } + + pub fn from_input<'a>( + external_id: String, + doc: Option, + attributes: &BTreeMap, + attribute_references: impl Iterator, + ) -> Result { + Ok(Self { + external_id, + doc, + attributes: attribute_references + .map(|x| { + attributes + .get(&*x.0) + .ok_or_else(|| ModelError::AttributeNotDefined { attr: x.0.to_owned() }) + .map(|attr| AttributeDef { + typ: x.0.to_owned(), + doc: attr.doc.to_owned(), + primitive_type: attr.typ, + }) + }) + .collect::, _>>()?, + }) + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EntityDef { - pub(crate) external_id: String, - pub(crate) doc: Option, - pub(crate) attributes: Vec, + pub(crate) external_id: String, + pub(crate) doc: Option, + pub(crate) attributes: Vec, } impl TypeName for &EntityDef { - fn as_type_name(&self) -> String { - type_name_for_kind("Entity", &self.external_id) - } + fn as_type_name(&self) -> String { + type_name_for_kind("Entity", &self.external_id) + } - fn preserve_inflection(&self) -> String { - preserve_inflection_for_kind("Entity", &self.external_id) - } + fn preserve_inflection(&self) -> String { + preserve_inflection_for_kind("Entity", &self.external_id) + } } impl EntityDef { - pub(crate) fn new( - external_id: impl AsRef, - doc: Option, - attributes: Vec, - ) -> Self { - Self { - external_id: external_id.as_ref().to_string(), - doc, - attributes, - } - } - - pub(crate) fn from_input<'a>( - external_id: String, - doc: Option, - attributes: &BTreeMap, - attribute_references: impl Iterator, - ) -> Result { - Ok(Self { - external_id, - doc, - attributes: attribute_references - .map(|x| { - attributes - .get(&*x.0) - .ok_or_else(|| ModelError::AttributeNotDefined { - attr: x.0.to_owned(), - }) - .map(|attr| AttributeDef { - typ: x.0.to_owned(), - doc: attr.doc.to_owned(), - primitive_type: attr.typ, - }) - }) - .collect::, _>>()?, - }) - } + pub(crate) fn new( + external_id: impl AsRef, + doc: Option, + attributes: Vec, + ) -> Self { + Self { external_id: external_id.as_ref().to_string(), doc, attributes } + } + + pub(crate) fn from_input<'a>( + external_id: String, + doc: Option, + attributes: &BTreeMap, + attribute_references: impl Iterator, + ) -> Result { + Ok(Self { + external_id, + doc, + attributes: attribute_references + .map(|x| { + attributes + .get(&*x.0) + .ok_or_else(|| ModelError::AttributeNotDefined { attr: x.0.to_owned() }) + .map(|attr| AttributeDef { + typ: x.0.to_owned(), + doc: attr.doc.to_owned(), + primitive_type: attr.typ, + }) + }) + .collect::, _>>()?, + }) + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ActivityDef { - pub(crate) external_id: String, - pub(crate) doc: Option, - pub(crate) attributes: Vec, + pub(crate) external_id: String, + pub(crate) doc: Option, + pub(crate) attributes: Vec, } impl TypeName for &ActivityDef { - fn as_type_name(&self) -> String { - type_name_for_kind("Activity", &self.external_id) - } + fn as_type_name(&self) -> String { + type_name_for_kind("Activity", &self.external_id) + } - fn preserve_inflection(&self) -> String { - preserve_inflection_for_kind("Activity", &self.external_id) - } + fn preserve_inflection(&self) -> String { + preserve_inflection_for_kind("Activity", &self.external_id) + } } impl ActivityDef { - pub(crate) fn new( - external_id: impl AsRef, - doc: Option, - attributes: Vec, - ) -> Self { - Self { - external_id: external_id.as_ref().to_string(), - doc, - attributes, - } - } - - pub(crate) fn from_input<'a>( - external_id: String, - doc: Option, - attributes: &BTreeMap, - attribute_references: impl Iterator, - ) -> Result { - Ok(Self { - external_id, - doc, - attributes: attribute_references - .map(|x| { - attributes - .get(&*x.0) - .ok_or_else(|| ModelError::AttributeNotDefined { - attr: x.0.to_owned(), - }) - .map(|attr| AttributeDef { - typ: x.0.to_owned(), - doc: attr.doc.to_owned(), - primitive_type: attr.typ, - }) - }) - .collect::, _>>()?, - }) - } + pub(crate) fn new( + external_id: impl AsRef, + doc: Option, + attributes: Vec, + ) -> Self { + Self { external_id: external_id.as_ref().to_string(), doc, attributes } + } + + pub(crate) fn from_input<'a>( + external_id: String, + doc: Option, + attributes: &BTreeMap, + attribute_references: impl Iterator, + ) -> Result { + Ok(Self { + external_id, + doc, + attributes: attribute_references + .map(|x| { + attributes + .get(&*x.0) + .ok_or_else(|| ModelError::AttributeNotDefined { attr: x.0.to_owned() }) + .map(|attr| AttributeDef { + typ: x.0.to_owned(), + doc: attr.doc.to_owned(), + primitive_type: attr.typ, + }) + }) + .collect::, _>>()?, + }) + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RoleDef { - pub(crate) external_id: String, + pub(crate) external_id: String, } impl RoleDef { - pub fn new(external_id: impl AsRef) -> Self { - Self { - external_id: external_id.as_ref().to_string(), - } - } - - pub fn from_role_file_input(external_id: String) -> Self { - RoleDef { external_id } - } + pub fn new(external_id: impl AsRef) -> Self { + Self { external_id: external_id.as_ref().to_string() } + } + + pub fn from_role_file_input(external_id: String) -> Self { + RoleDef { external_id } + } } impl TypeName for &RoleDef { - fn as_type_name(&self) -> String { - to_pascal_case(&self.external_id) - } + fn as_type_name(&self) -> String { + to_pascal_case(&self.external_id) + } - fn preserve_inflection(&self) -> String { - self.external_id.clone() - } + fn preserve_inflection(&self) -> String { + self.external_id.clone() + } } fn type_name_for_kind(kind: &str, id: &str) -> String { - if id == format!("Prov{kind}") { - id.to_string() - } else { - match (id.chars().next(), id.chars().nth(1), &id[1..]) { - (_, Some(c), _) if c.is_uppercase() => format!("{id}{kind}"), - (Some(first), _, body) => format!("{}{}{}", first.to_uppercase(), body, kind), - _ => format!("{}{}", to_pascal_case(id), kind), - } - } + if id == format!("Prov{kind}") { + id.to_string() + } else { + match (id.chars().next(), id.chars().nth(1), &id[1..]) { + (_, Some(c), _) if c.is_uppercase() => format!("{id}{kind}"), + (Some(first), _, body) => format!("{}{}{}", first.to_uppercase(), body, kind), + _ => format!("{}{}", to_pascal_case(id), kind), + } + } } fn preserve_inflection_for_kind(kind: &str, id: &str) -> String { - match (id.chars().next(), id.chars().nth(1), &id[1..]) { - (_, Some(c), _) if c.is_uppercase() => format!("{id}{kind}"), - (Some(first), _, body) => format!("{}{}{}", first.to_lowercase(), body, kind), - _ => to_camel_case(&format!("{id}{kind}")), - } + match (id.chars().next(), id.chars().nth(1), &id[1..]) { + (_, Some(c), _) if c.is_uppercase() => format!("{id}{kind}"), + (Some(first), _, body) => format!("{}{}{}", first.to_lowercase(), body, kind), + _ => to_camel_case(&format!("{id}{kind}")), + } } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ChronicleDomainDef { - name: String, - pub(crate) attributes: Vec, - pub(crate) agents: Vec, - pub(crate) entities: Vec, - pub(crate) activities: Vec, - pub(crate) roles_doc: Option, - pub(crate) roles: Vec, + name: String, + pub(crate) attributes: Vec, + pub(crate) agents: Vec, + pub(crate) entities: Vec, + pub(crate) activities: Vec, + pub(crate) roles_doc: Option, + pub(crate) roles: Vec, } pub struct AgentBuilder<'a>(&'a ChronicleDomainDef, AgentDef); impl<'a> AgentBuilder<'a> { - pub fn new( - domain: &'a ChronicleDomainDef, - external_id: impl AsRef, - doc: Option, - ) -> Self { - Self(domain, AgentDef::new(external_id, doc, vec![])) - } - - pub fn with_attribute(mut self, typ: impl AsRef) -> Result { - let attr = self - .0 - .attribute(typ.as_ref()) - .ok_or(ModelError::AttributeNotDefined { - attr: typ.as_ref().to_string(), - })?; - self.1.attributes.push(attr); - Ok(self) - } + pub fn new( + domain: &'a ChronicleDomainDef, + external_id: impl AsRef, + doc: Option, + ) -> Self { + Self(domain, AgentDef::new(external_id, doc, vec![])) + } + + pub fn with_attribute(mut self, typ: impl AsRef) -> Result { + let attr = self + .0 + .attribute(typ.as_ref()) + .ok_or(ModelError::AttributeNotDefined { attr: typ.as_ref().to_string() })?; + self.1.attributes.push(attr); + Ok(self) + } } impl<'a> From> for AgentDef { - fn from(val: AgentBuilder<'a>) -> Self { - val.1 - } + fn from(val: AgentBuilder<'a>) -> Self { + val.1 + } } pub struct EntityBuilder<'a>(&'a ChronicleDomainDef, EntityDef); impl<'a> EntityBuilder<'a> { - pub(crate) fn new( - domain: &'a ChronicleDomainDef, - external_id: impl AsRef, - doc: Option, - ) -> Self { - Self(domain, EntityDef::new(external_id, doc, vec![])) - } - - pub(crate) fn with_attribute(mut self, typ: impl AsRef) -> Result { - let attr = self - .0 - .attribute(typ.as_ref()) - .ok_or(ModelError::AttributeNotDefined { - attr: typ.as_ref().to_string(), - })?; - self.1.attributes.push(attr); - Ok(self) - } + pub(crate) fn new( + domain: &'a ChronicleDomainDef, + external_id: impl AsRef, + doc: Option, + ) -> Self { + Self(domain, EntityDef::new(external_id, doc, vec![])) + } + + pub(crate) fn with_attribute(mut self, typ: impl AsRef) -> Result { + let attr = self + .0 + .attribute(typ.as_ref()) + .ok_or(ModelError::AttributeNotDefined { attr: typ.as_ref().to_string() })?; + self.1.attributes.push(attr); + Ok(self) + } } impl<'a> From> for EntityDef { - fn from(val: EntityBuilder<'a>) -> Self { - val.1 - } + fn from(val: EntityBuilder<'a>) -> Self { + val.1 + } } pub struct ActivityBuilder<'a>(&'a ChronicleDomainDef, ActivityDef); impl<'a> ActivityBuilder<'a> { - pub(crate) fn new( - domain: &'a ChronicleDomainDef, - external_id: impl AsRef, - doc: Option, - ) -> Self { - Self(domain, ActivityDef::new(external_id, doc, vec![])) - } - - pub(crate) fn with_attribute(mut self, typ: impl AsRef) -> Result { - let attr = self - .0 - .attribute(typ.as_ref()) - .ok_or(ModelError::AttributeNotDefined { - attr: typ.as_ref().to_string(), - })?; - self.1.attributes.push(attr); - Ok(self) - } + pub(crate) fn new( + domain: &'a ChronicleDomainDef, + external_id: impl AsRef, + doc: Option, + ) -> Self { + Self(domain, ActivityDef::new(external_id, doc, vec![])) + } + + pub(crate) fn with_attribute(mut self, typ: impl AsRef) -> Result { + let attr = self + .0 + .attribute(typ.as_ref()) + .ok_or(ModelError::AttributeNotDefined { attr: typ.as_ref().to_string() })?; + self.1.attributes.push(attr); + Ok(self) + } } impl<'a> From> for ActivityDef { - fn from(val: ActivityBuilder<'a>) -> Self { - val.1 - } + fn from(val: ActivityBuilder<'a>) -> Self { + val.1 + } } pub struct Builder(ChronicleDomainDef); impl Builder { - pub(crate) fn new(name: impl AsRef) -> Self { - Builder(ChronicleDomainDef { - name: name.as_ref().to_string(), - ..Default::default() - }) - } - - pub(crate) fn with_attribute_type( - mut self, - external_id: impl AsRef, - doc: Option, - typ: PrimitiveType, - ) -> Result { - self.0.attributes.push(AttributeDef { - typ: external_id.as_ref().to_string(), - doc, - primitive_type: typ, - }); - - Ok(self) - } - - pub(crate) fn with_agent( - mut self, - external_id: impl AsRef, - doc: Option, - b: impl FnOnce(AgentBuilder<'_>) -> Result, ModelError>, - ) -> Result { - self.0.agents.push( - b(AgentBuilder( - &self.0, - AgentDef::new(external_id, doc, vec![]), - ))? - .into(), - ); - Ok(self) - } - - pub(crate) fn with_entity( - mut self, - external_id: impl AsRef, - doc: Option, - b: impl FnOnce(EntityBuilder<'_>) -> Result, ModelError>, - ) -> Result { - self.0.entities.push( - b(EntityBuilder( - &self.0, - EntityDef::new(external_id, doc, vec![]), - ))? - .into(), - ); - Ok(self) - } - - pub(crate) fn with_activity( - mut self, - external_id: impl AsRef, - doc: Option, - b: impl FnOnce(ActivityBuilder<'_>) -> Result, ModelError>, - ) -> Result { - self.0.activities.push( - b(ActivityBuilder( - &self.0, - ActivityDef::new(external_id, doc, vec![]), - ))? - .into(), - ); - - Ok(self) - } - - pub(crate) fn with_role(mut self, external_id: impl AsRef) -> Result { - self.0.roles.push(RoleDef::new(external_id)); - - Ok(self) - } - - pub fn build(self) -> ChronicleDomainDef { - self.0 - } + pub(crate) fn new(name: impl AsRef) -> Self { + Builder(ChronicleDomainDef { name: name.as_ref().to_string(), ..Default::default() }) + } + + pub(crate) fn with_attribute_type( + mut self, + external_id: impl AsRef, + doc: Option, + typ: PrimitiveType, + ) -> Result { + self.0.attributes.push(AttributeDef { + typ: external_id.as_ref().to_string(), + doc, + primitive_type: typ, + }); + + Ok(self) + } + + pub(crate) fn with_agent( + mut self, + external_id: impl AsRef, + doc: Option, + b: impl FnOnce(AgentBuilder<'_>) -> Result, ModelError>, + ) -> Result { + self.0 + .agents + .push(b(AgentBuilder(&self.0, AgentDef::new(external_id, doc, vec![])))?.into()); + Ok(self) + } + + pub(crate) fn with_entity( + mut self, + external_id: impl AsRef, + doc: Option, + b: impl FnOnce(EntityBuilder<'_>) -> Result, ModelError>, + ) -> Result { + self.0 + .entities + .push(b(EntityBuilder(&self.0, EntityDef::new(external_id, doc, vec![])))?.into()); + Ok(self) + } + + pub(crate) fn with_activity( + mut self, + external_id: impl AsRef, + doc: Option, + b: impl FnOnce(ActivityBuilder<'_>) -> Result, ModelError>, + ) -> Result { + self.0 + .activities + .push(b(ActivityBuilder(&self.0, ActivityDef::new(external_id, doc, vec![])))?.into()); + + Ok(self) + } + + pub(crate) fn with_role(mut self, external_id: impl AsRef) -> Result { + self.0.roles.push(RoleDef::new(external_id)); + + Ok(self) + } + + pub fn build(self) -> ChronicleDomainDef { + self.0 + } } #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] pub struct AttributeFileInput { - doc: Option, - #[serde(rename = "type")] - typ: PrimitiveType, + doc: Option, + #[serde(rename = "type")] + typ: PrimitiveType, } impl From<&AttributeDef> for AttributeFileInput { - fn from(attr: &AttributeDef) -> Self { - Self { - doc: attr.doc.to_owned(), - typ: attr.primitive_type, - } - } + fn from(attr: &AttributeDef) -> Self { + Self { doc: attr.doc.to_owned(), typ: attr.primitive_type } + } } #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] @@ -560,248 +504,245 @@ pub struct AttributeRef(pub String); #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] pub struct ResourceDef { - pub(crate) doc: Option, - pub(crate) attributes: Vec, + pub(crate) doc: Option, + pub(crate) attributes: Vec, } impl From<&AgentDef> for ResourceDef { - fn from(agent: &AgentDef) -> Self { - Self { - doc: agent.doc.to_owned(), - attributes: agent - .attributes - .iter() - .map(|attr| AttributeRef(attr.typ.to_owned())) - .collect(), - } - } + fn from(agent: &AgentDef) -> Self { + Self { + doc: agent.doc.to_owned(), + attributes: agent + .attributes + .iter() + .map(|attr| AttributeRef(attr.typ.to_owned())) + .collect(), + } + } } impl From<&EntityDef> for ResourceDef { - fn from(entity: &EntityDef) -> Self { - Self { - doc: entity.doc.to_owned(), - attributes: entity - .attributes - .iter() - .map(|attr| AttributeRef(attr.typ.to_owned())) - .collect(), - } - } + fn from(entity: &EntityDef) -> Self { + Self { + doc: entity.doc.to_owned(), + attributes: entity + .attributes + .iter() + .map(|attr| AttributeRef(attr.typ.to_owned())) + .collect(), + } + } } impl From<&ActivityDef> for ResourceDef { - fn from(activity: &ActivityDef) -> Self { - Self { - doc: activity.doc.to_owned(), - attributes: activity - .attributes - .iter() - .map(|attr| AttributeRef(attr.typ.to_owned())) - .collect(), - } - } + fn from(activity: &ActivityDef) -> Self { + Self { + doc: activity.doc.to_owned(), + attributes: activity + .attributes + .iter() + .map(|attr| AttributeRef(attr.typ.to_owned())) + .collect(), + } + } } #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default)] pub struct DomainFileInput { - pub(crate) name: String, - pub(crate) attributes: BTreeMap, - pub(crate) agents: BTreeMap, - pub(crate) entities: BTreeMap, - pub(crate) activities: BTreeMap, - pub(crate) roles_doc: Option, - pub(crate) roles: Vec, + pub(crate) name: String, + pub(crate) attributes: BTreeMap, + pub(crate) agents: BTreeMap, + pub(crate) entities: BTreeMap, + pub(crate) activities: BTreeMap, + pub(crate) roles_doc: Option, + pub(crate) roles: Vec, } impl DomainFileInput { - pub(crate) fn new(name: impl AsRef) -> Self { - DomainFileInput { - name: name.as_ref().to_string(), - ..Default::default() - } - } + pub(crate) fn new(name: impl AsRef) -> Self { + DomainFileInput { name: name.as_ref().to_string(), ..Default::default() } + } } impl FromStr for DomainFileInput { - type Err = ModelError; - - fn from_str(s: &str) -> Result { - match serde_json::from_str::(s) { - Err(_) => Ok(serde_yaml::from_str::(s)?), - Ok(domain) => Ok(domain), - } - } + type Err = ModelError; + + fn from_str(s: &str) -> Result { + match serde_json::from_str::(s) { + Err(_) => Ok(serde_yaml::from_str::(s)?), + Ok(domain) => Ok(domain), + } + } } impl From<&ChronicleDomainDef> for DomainFileInput { - fn from(domain: &ChronicleDomainDef) -> Self { - let mut file = Self::new(&domain.name); - - for attr in &domain.attributes { - let external_id = attr.typ.to_string(); - file.attributes.insert(external_id, attr.into()); - } - - file.agents = domain - .agents - .iter() - .map(|x| (x.external_id.clone(), ResourceDef::from(x))) - .collect(); - - file.entities = domain - .entities - .iter() - .map(|x| (x.external_id.clone(), ResourceDef::from(x))) - .collect(); - - file.activities = domain - .activities - .iter() - .map(|x| (x.external_id.clone(), ResourceDef::from(x))) - .collect(); - - file.roles_doc = domain.roles_doc.to_owned(); - - file.roles = domain.roles.iter().map(|x| x.as_type_name()).collect(); - - file - } + fn from(domain: &ChronicleDomainDef) -> Self { + let mut file = Self::new(&domain.name); + + for attr in &domain.attributes { + let external_id = attr.typ.to_string(); + file.attributes.insert(external_id, attr.into()); + } + + file.agents = domain + .agents + .iter() + .map(|x| (x.external_id.clone(), ResourceDef::from(x))) + .collect(); + + file.entities = domain + .entities + .iter() + .map(|x| (x.external_id.clone(), ResourceDef::from(x))) + .collect(); + + file.activities = domain + .activities + .iter() + .map(|x| (x.external_id.clone(), ResourceDef::from(x))) + .collect(); + + file.roles_doc = domain.roles_doc.to_owned(); + + file.roles = domain.roles.iter().map(|x| x.as_type_name()).collect(); + + file + } } impl ChronicleDomainDef { - pub(crate) fn build(external_id: &str) -> Builder { - Builder::new(external_id) - } - - fn attribute(&self, attr: &str) -> Option { - self.attributes.iter().find(|a| a.typ == attr).cloned() - } - - pub fn from_input_string(s: &str) -> Result { - ChronicleDomainDef::from_str(s) - } - - fn from_json(file: &str) -> Result { - let model = serde_json::from_str::(file)?; - Self::from_model(model) - } - - fn from_yaml(file: &str) -> Result { - let model = serde_yaml::from_str::(file)?; - Self::from_model(model) - } - - pub fn from_file(path: impl AsRef) -> Result { - let path = path.as_ref(); - - let file: String = std::fs::read_to_string(path)?; - - match path.extension() { - Some(ext) if ext == "json" => Self::from_json(&file), - _ => Self::from_yaml(&file), - } - } - - fn from_model(model: DomainFileInput) -> Result { - let mut builder = Builder::new(model.name); - - for (external_id, attr) in model.attributes.iter() { - builder = builder.with_attribute_type(external_id, attr.doc.to_owned(), attr.typ)?; - } - - for (external_id, def) in model.agents { - builder.0.agents.push(AgentDef::from_input( - external_id, - def.doc, - &model.attributes, - def.attributes.iter(), - )?) - } - - for (external_id, def) in model.entities { - builder.0.entities.push(EntityDef::from_input( - external_id, - def.doc, - &model.attributes, - def.attributes.iter(), - )?) - } - - for (external_id, def) in model.activities { - builder.0.activities.push(ActivityDef::from_input( - external_id, - def.doc, - &model.attributes, - def.attributes.iter(), - )?) - } - - if model.roles_doc.is_some() { - builder.0.roles_doc = model.roles_doc; - } - - for role in model.roles { - builder.0.roles.push(RoleDef::from_role_file_input(role)); - } - - Ok(builder.build()) - } - - pub(crate) fn to_json_string(&self) -> Result { - let input: DomainFileInput = self.into(); - let json = serde_json::to_string(&input)?; - Ok(json) - } - - fn to_yaml_string(&self) -> Result { - let input: DomainFileInput = self.into(); - let yaml = serde_yaml::to_string(&input)?; - Ok(yaml) - } + pub(crate) fn build(external_id: &str) -> Builder { + Builder::new(external_id) + } + + fn attribute(&self, attr: &str) -> Option { + self.attributes.iter().find(|a| a.typ == attr).cloned() + } + + pub fn from_input_string(s: &str) -> Result { + ChronicleDomainDef::from_str(s) + } + + fn from_json(file: &str) -> Result { + let model = serde_json::from_str::(file)?; + Self::from_model(model) + } + + fn from_yaml(file: &str) -> Result { + let model = serde_yaml::from_str::(file)?; + Self::from_model(model) + } + + pub fn from_file(path: impl AsRef) -> Result { + let path = path.as_ref(); + + let file: String = std::fs::read_to_string(path)?; + + match path.extension() { + Some(ext) if ext == "json" => Self::from_json(&file), + _ => Self::from_yaml(&file), + } + } + + fn from_model(model: DomainFileInput) -> Result { + let mut builder = Builder::new(model.name); + + for (external_id, attr) in model.attributes.iter() { + builder = builder.with_attribute_type(external_id, attr.doc.to_owned(), attr.typ)?; + } + + for (external_id, def) in model.agents { + builder.0.agents.push(AgentDef::from_input( + external_id, + def.doc, + &model.attributes, + def.attributes.iter(), + )?) + } + + for (external_id, def) in model.entities { + builder.0.entities.push(EntityDef::from_input( + external_id, + def.doc, + &model.attributes, + def.attributes.iter(), + )?) + } + + for (external_id, def) in model.activities { + builder.0.activities.push(ActivityDef::from_input( + external_id, + def.doc, + &model.attributes, + def.attributes.iter(), + )?) + } + + if model.roles_doc.is_some() { + builder.0.roles_doc = model.roles_doc; + } + + for role in model.roles { + builder.0.roles.push(RoleDef::from_role_file_input(role)); + } + + Ok(builder.build()) + } + + pub(crate) fn to_json_string(&self) -> Result { + let input: DomainFileInput = self.into(); + let json = serde_json::to_string(&input)?; + Ok(json) + } + + fn to_yaml_string(&self) -> Result { + let input: DomainFileInput = self.into(); + let yaml = serde_yaml::to_string(&input)?; + Ok(yaml) + } } /// Parse from a yaml formatted string impl FromStr for ChronicleDomainDef { - type Err = ModelError; + type Err = ModelError; - fn from_str(s: &str) -> Result { - Self::from_yaml(s) - } + fn from_str(s: &str) -> Result { + Self::from_yaml(s) + } } #[cfg(test)] pub mod test { - use super::{ChronicleDomainDef, DomainFileInput, EntityDef}; + use super::{ChronicleDomainDef, DomainFileInput, EntityDef}; - use std::cmp::Ordering; + use std::cmp::Ordering; - impl PartialEq for EntityDef { - fn eq(&self, other: &Self) -> bool { - self.external_id == other.external_id - } - } + impl PartialEq for EntityDef { + fn eq(&self, other: &Self) -> bool { + self.external_id == other.external_id + } + } - impl Eq for EntityDef {} + impl Eq for EntityDef {} - impl PartialOrd for EntityDef { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } + impl PartialOrd for EntityDef { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } - impl Ord for EntityDef { - fn cmp(&self, other: &Self) -> Ordering { - self.external_id.cmp(&other.external_id) - } - } + impl Ord for EntityDef { + fn cmp(&self, other: &Self) -> Ordering { + self.external_id.cmp(&other.external_id) + } + } - use assert_fs::prelude::*; + use assert_fs::prelude::*; - fn create_test_yaml_file() -> Result> { - let file = assert_fs::NamedTempFile::new("test.yml")?; - file.write_str( - r#" + fn create_test_yaml_file() -> Result> { + let file = assert_fs::NamedTempFile::new("test.yml")?; + file.write_str( + r#" name: "chronicle" attributes: String: @@ -841,16 +782,16 @@ pub mod test { roles: - drummer "#, - )?; - Ok(file) - } - - // more than one entity will be in no particular order - fn create_test_yaml_file_single_entity( - ) -> Result> { - let file = assert_fs::NamedTempFile::new("test.yml")?; - file.write_str( - r#" + )?; + Ok(file) + } + + // more than one entity will be in no particular order + fn create_test_yaml_file_single_entity( + ) -> Result> { + let file = assert_fs::NamedTempFile::new("test.yml")?; + file.write_str( + r#" name: "test" attributes: String: @@ -870,14 +811,14 @@ pub mod test { roles: - drummer "#, - )?; - Ok(file) - } - - fn create_test_json_file() -> Result> { - let file = assert_fs::NamedTempFile::new("test.json")?; - file.write_str( - r#" { + )?; + Ok(file) + } + + fn create_test_json_file() -> Result> { + let file = assert_fs::NamedTempFile::new("test.json")?; + file.write_str( + r#" { "name": "chronicle", "attributes": { "String": { @@ -913,19 +854,19 @@ pub mod test { "roles" : ["drummer"] } "#, - )?; - Ok(file) - } + )?; + Ok(file) + } - #[test] - fn json_from_file() -> Result<(), Box> { - let file = create_test_json_file()?; + #[test] + fn json_from_file() -> Result<(), Box> { + let file = create_test_json_file()?; - let mut domain = ChronicleDomainDef::from_file(file.path()).unwrap(); + let mut domain = ChronicleDomainDef::from_file(file.path()).unwrap(); - domain.entities.sort(); + domain.entities.sort(); - insta::assert_yaml_snapshot!(domain, @r###" + insta::assert_yaml_snapshot!(domain, @r###" --- name: chronicle attributes: @@ -964,18 +905,18 @@ pub mod test { - external_id: drummer "###); - Ok(()) - } + Ok(()) + } - #[test] - fn yaml_from_file() -> Result<(), Box> { - let file = create_test_yaml_file()?; + #[test] + fn yaml_from_file() -> Result<(), Box> { + let file = create_test_yaml_file()?; - let mut domain = ChronicleDomainDef::from_file(file.path()).unwrap(); + let mut domain = ChronicleDomainDef::from_file(file.path()).unwrap(); - domain.entities.sort(); + domain.entities.sort(); - insta::assert_yaml_snapshot!(domain, @r###" + insta::assert_yaml_snapshot!(domain, @r###" --- name: chronicle attributes: @@ -1056,18 +997,18 @@ pub mod test { - external_id: drummer "###); - Ok(()) - } + Ok(()) + } - use std::str::FromStr; + use std::str::FromStr; - #[test] - fn test_chronicle_domain_def_from_str() -> Result<(), Box> { - let file = create_test_yaml_file()?; - let s: String = std::fs::read_to_string(file.path())?; - let domain = ChronicleDomainDef::from_str(&s)?; + #[test] + fn test_chronicle_domain_def_from_str() -> Result<(), Box> { + let file = create_test_yaml_file()?; + let s: String = std::fs::read_to_string(file.path())?; + let domain = ChronicleDomainDef::from_str(&s)?; - insta::assert_yaml_snapshot!(domain, @r###" + insta::assert_yaml_snapshot!(domain, @r###" --- name: chronicle attributes: @@ -1148,17 +1089,17 @@ pub mod test { - external_id: drummer "###); - Ok(()) - } + Ok(()) + } - #[test] - fn test_from_domain_for_file_input() -> Result<(), Box> { - let file = create_test_yaml_file_single_entity()?; - let s: String = std::fs::read_to_string(file.path())?; - let domain = ChronicleDomainDef::from_str(&s)?; - let input = DomainFileInput::from(&domain); + #[test] + fn test_from_domain_for_file_input() -> Result<(), Box> { + let file = create_test_yaml_file_single_entity()?; + let s: String = std::fs::read_to_string(file.path())?; + let domain = ChronicleDomainDef::from_str(&s)?; + let input = DomainFileInput::from(&domain); - insta::assert_yaml_snapshot!(input, @r###" + insta::assert_yaml_snapshot!(input, @r###" --- name: test attributes: @@ -1185,33 +1126,33 @@ pub mod test { - Drummer "###); - Ok(()) - } + Ok(()) + } - use super::{AttributeDef, AttributeFileInput, PrimitiveType}; + use super::{AttributeDef, AttributeFileInput, PrimitiveType}; - #[test] - fn test_from_attribute_def_for_attribute_file_input() { - let attr = AttributeDef { - typ: "string".to_string(), - doc: None, - primitive_type: PrimitiveType::String, - }; - let input = AttributeFileInput::from(&attr); - insta::assert_yaml_snapshot!(input, @r###" + #[test] + fn test_from_attribute_def_for_attribute_file_input() { + let attr = AttributeDef { + typ: "string".to_string(), + doc: None, + primitive_type: PrimitiveType::String, + }; + let input = AttributeFileInput::from(&attr); + insta::assert_yaml_snapshot!(input, @r###" --- doc: ~ type: String "###); - } + } - #[test] - fn test_to_json_string() -> Result<(), Box> { - let file = create_test_yaml_file_single_entity()?; - let s: String = std::fs::read_to_string(file.path())?; - let domain = ChronicleDomainDef::from_str(&s)?; + #[test] + fn test_to_json_string() -> Result<(), Box> { + let file = create_test_yaml_file_single_entity()?; + let s: String = std::fs::read_to_string(file.path())?; + let domain = ChronicleDomainDef::from_str(&s)?; - insta::assert_yaml_snapshot!(domain, @r###" + insta::assert_yaml_snapshot!(domain, @r###" --- name: test attributes: @@ -1244,16 +1185,16 @@ pub mod test { - external_id: drummer "###); - Ok(()) - } + Ok(()) + } - #[test] - fn test_to_yaml_string() -> Result<(), Box> { - let file = create_test_yaml_file_single_entity()?; - let s: String = std::fs::read_to_string(file.path())?; - let domain = ChronicleDomainDef::from_str(&s)?; + #[test] + fn test_to_yaml_string() -> Result<(), Box> { + let file = create_test_yaml_file_single_entity()?; + let s: String = std::fs::read_to_string(file.path())?; + let domain = ChronicleDomainDef::from_str(&s)?; - insta::assert_yaml_snapshot!(domain, @r###" + insta::assert_yaml_snapshot!(domain, @r###" --- name: test attributes: @@ -1286,14 +1227,14 @@ pub mod test { - external_id: drummer "###); - Ok(()) - } + Ok(()) + } - fn create_test_yaml_file_with_acronyms( - ) -> Result> { - let file = assert_fs::NamedTempFile::new("test.yml")?; - file.write_str( - r#" + fn create_test_yaml_file_with_acronyms( + ) -> Result> { + let file = assert_fs::NamedTempFile::new("test.yml")?; + file.write_str( + r#" name: "evidence" attributes: Content: @@ -1349,19 +1290,19 @@ pub mod test { - RESEARCHER - EDITOR "#, - )?; - Ok(file) - } - - #[test] - fn test_from_domain_for_file_input_with_inflections() -> Result<(), Box> - { - let file = create_test_yaml_file_with_acronyms()?; - let s: String = std::fs::read_to_string(file.path())?; - let domain = ChronicleDomainDef::from_str(&s)?; - let input = DomainFileInput::from(&domain); - - insta::assert_yaml_snapshot!(input, @r###" + )?; + Ok(file) + } + + #[test] + fn test_from_domain_for_file_input_with_inflections() -> Result<(), Box> + { + let file = create_test_yaml_file_with_acronyms()?; + let s: String = std::fs::read_to_string(file.path())?; + let domain = ChronicleDomainDef::from_str(&s)?; + let input = DomainFileInput::from(&domain); + + insta::assert_yaml_snapshot!(input, @r###" --- name: evidence attributes: @@ -1435,13 +1376,13 @@ pub mod test { - Researcher - Editor "###); - Ok(()) - } + Ok(()) + } - fn create_test_yaml_file_with_docs( - ) -> Result> { - let file = assert_fs::NamedTempFile::new("test.yml")?; - file.write_str( + fn create_test_yaml_file_with_docs( + ) -> Result> { + let file = assert_fs::NamedTempFile::new("test.yml")?; + file.write_str( r#" name: Artworld attributes: @@ -1570,17 +1511,17 @@ pub mod test { - CREATOR "#, )?; - Ok(file) - } + Ok(file) + } - #[test] - fn test_from_domain_for_file_input_with_docs() -> Result<(), Box> { - let file = create_test_yaml_file_with_docs()?; - let s: String = std::fs::read_to_string(file.path())?; - let domain = ChronicleDomainDef::from_str(&s)?; - let input = DomainFileInput::from(&domain); + #[test] + fn test_from_domain_for_file_input_with_docs() -> Result<(), Box> { + let file = create_test_yaml_file_with_docs()?; + let s: String = std::fs::read_to_string(file.path())?; + let domain = ChronicleDomainDef::from_str(&s)?; + let input = DomainFileInput::from(&domain); - insta::assert_yaml_snapshot!(input, @r###" + insta::assert_yaml_snapshot!(input, @r###" --- name: Artworld attributes: @@ -1641,6 +1582,6 @@ pub mod test { - Seller - Creator "###); - Ok(()) - } + Ok(()) + } } diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index f31f35184..897936e57 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -7,60 +7,45 @@ version = "0.7.5" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = { workspace = true } -async-graphql = { workspace = true, features = [ +anyhow = { version="1", default-features=false} +async-graphql = { version="5.0.9", features = [ "opentelemetry", "chrono", "unblock", "default", - "uuid", -] } + "uuid"], optional=true } +sp-std= {version="11.0.0", optional=true} async-trait = { workspace = true } -chronicle-signing = { workspace = true } -chrono = { workspace = true } -custom_error = { workspace = true } -derivative = { workspace = true } -diesel = { workspace = true } -futures = { workspace = true } -glob = { workspace = true } -hashbrown = { workspace = true } -hex = { workspace = true } -iref = { workspace = true } -iref-enum = { workspace = true } -json-ld = { workspace = true } -json-syntax = { workspace = true } -k256 = { workspace = true } -lazy_static = { workspace = true } -locspan = { workspace = true } -mime = { workspace = true } -opa = { workspace = true } -opa-tp-protocol = { path = "../opa-tp-protocol" } -openssl = { workspace = true } -percent-encoding = { workspace = true } -pkcs8 = { workspace = true } -prost = "0.10.0" -frame-support = {workspace=true} -scale-info = {workspace=true} -parity-scale-codec = {workspace = true} +chrono = { version="0.4", default-features=false,features=["serde","alloc"] } +diesel = { version="2.0.0-rc.0",features = [ + "postgres", + "uuid", + "chrono", + "r2d2", +], optional=true} +iref= {version="2.2", optional = true} +futures = { version="0.3", default-features=false} +hex = { version="0.4", default-features=false, features=["alloc"]} +rdf-types={ version="0.14", optional=true} +json-ld = { version="0.14", optional=true} +hashbrown = {version="0.13", optional=true} +json-syntax = { version="0.9", features=["serde","serde_json"], optional = true } +k256 = { version="0.11.3",default-features=false,features=["ecdsa","pkcs8"] } +lazy_static = { version="1.4" } +locspan = { version="0.7", optional=true} +mime = { version="0.3", optional=true } +scale-info = {version="2.10.0", default-features=false, features=["derive"]} +parity-scale-codec = {version = "3.6.5", default-features=false,features=["derive","max-encoded-len"]} newtype-derive-2018 = {workspace=true} macro-attr-2018 = {workspace=true} -r2d2 = { workspace = true } -rand = { workspace = true } -rand_core = { workspace = true } -rdf-types = { workspace = true } -reqwest = { workspace = true } -rust-embed = { workspace = true } -serde = { workspace = true } -serde_derive = { workspace = true } -serde_json = { workspace = true } -static-iref = { workspace = true } -testcontainers = { workspace = true } +serde = { version="1.0", default-features=false } +serde_derive = { version="1.0",default-features=false } +serde_json = { version="1.0", default-features=false } +iri-string = {version="0.7",default-features=false, features=["alloc"]} thiserror = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } -url = { workspace = true } -uuid = { workspace = true } -zmq = { workspace = true } +thiserror-no-std = {version="2.0.2" } +tracing = { version="0.1.40", default-features=false, features=["attributes"]} +uuid = {version="1.5.0", default-features=false, features=["serde"]} [build-dependencies] glob = { workspace = true } @@ -68,15 +53,20 @@ lazy_static = { workspace = true } serde_json = { workspace = true } [dev-dependencies] +tokio={workspace=true} +testcontainers = { workspace = true } criterion = { workspace = true } insta = { workspace = true, features = ["json"] } mockito = { workspace = true } proptest = { workspace = true } tempfile = { workspace = true } -[[bench]] -harness = false -name = "opa_executor" - [features] -strict = [] +std = ["k256/std","tracing/std","serde/std","serde_json/std","hex/std","futures/std","iri-string/std","chrono/std","anyhow/std"] +default = ["std","graphql-bindings","diesel-bindings","json-ld"] +# Enable parity support, annoyingly lazy_static has a non standard way of enabling non_std +parity = ["lazy_static/spin_no_std"] +# At this point, LD should be a seperate crate +json-ld = ["dep:json-ld","dep:json-syntax","dep:rdf-types","dep:hashbrown","dep:mime","dep:locspan","dep:iref"] +graphql-bindings = ["async-graphql"] +diesel-bindings = ["diesel"] diff --git a/crates/common/benches/opa_executor.rs b/crates/common/benches/opa_executor.rs deleted file mode 100644 index dee20a4fd..000000000 --- a/crates/common/benches/opa_executor.rs +++ /dev/null @@ -1,82 +0,0 @@ -use common::{ - identity::{AuthId, IdentityContext, OpaData}, - opa::{CliPolicyLoader, OpaExecutor, OpaExecutorError, WasmtimeOpaExecutor}, -}; -use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; -use serde_json::Value; -use tokio::runtime::Runtime; - -fn load_wasm_from_file() -> Result<(), OpaExecutorError> { - let wasm = "allow_transactions"; - let entrypoint = "allow_transactions/allowed_users"; - CliPolicyLoader::from_embedded_policy(wasm, entrypoint)?; - Ok(()) -} - -fn bench_policy_loader_load_policy(c: &mut Criterion) { - c.bench_function("load_wasm_from_embedded", |b| { - b.iter(|| load_wasm_from_file().unwrap()); - }); -} - -fn build_executor_from_loader(loader: &CliPolicyLoader) -> Result<(), OpaExecutorError> { - let _executor = WasmtimeOpaExecutor::from_loader(loader)?; - Ok(()) -} - -fn bench_build_opa_executor(c: &mut Criterion) { - let input = { - let wasm = "allow_transactions"; - let entrypoint = "allow_transactions.allowed_users"; - CliPolicyLoader::from_embedded_policy(wasm, entrypoint).unwrap() - }; - c.bench_with_input( - BenchmarkId::new("build_executor_from_loader", "CliPolicyLoader"), - &input, - |b, input| { - b.iter(|| build_executor_from_loader(input)); - }, - ); -} - -async fn evaluate( - executor: &mut WasmtimeOpaExecutor, - id: &AuthId, - data: &OpaData, -) -> Result<(), OpaExecutorError> { - executor.evaluate(id, data).await?; - Ok(()) -} - -fn bench_evaluate_policy(c: &mut Criterion) { - c.bench_function("evaluate", |b| { - b.iter_batched_ref( - || { - let wasm = "allow_transactions"; - let entrypoint = "allow_transactions.allowed_users"; - let loader = CliPolicyLoader::from_embedded_policy(wasm, entrypoint).unwrap(); - let id = AuthId::chronicle(); - let data = OpaData::Operation(IdentityContext::new( - AuthId::chronicle(), - Value::default(), - Value::default(), - )); - (WasmtimeOpaExecutor::from_loader(&loader).unwrap(), id, data) - }, - |(executor, id, data)| { - let rt = Runtime::new().unwrap(); - let handle = rt.handle(); - handle.block_on(async move { evaluate(executor, id, data).await.unwrap() }) - }, - BatchSize::SmallInput, - ); - }); -} - -criterion_group!( - benches, - bench_policy_loader_load_policy, - bench_build_opa_executor, - bench_evaluate_policy -); -criterion_main!(benches); diff --git a/crates/common/build.rs b/crates/common/build.rs index 3eb1ccf7c..2c02a6c5c 100644 --- a/crates/common/build.rs +++ b/crates/common/build.rs @@ -3,24 +3,21 @@ use std::{env, fs, io::Result, path::PathBuf}; include!("./src/context.rs"); fn main() -> Result<()> { - let out_str = env::var("OUT_DIR").unwrap(); - let out_path = PathBuf::from(&out_str); - let mut out_path = out_path.ancestors().nth(3).unwrap().to_owned(); - out_path.push("assets"); + let out_str = env::var("OUT_DIR").unwrap(); + let out_path = PathBuf::from(&out_str); + let mut out_path = out_path.ancestors().nth(3).unwrap().to_owned(); + out_path.push("assets"); - if !out_path.exists() { - fs::create_dir(&out_path).expect("Could not create assets dir"); - } + if !out_path.exists() { + fs::create_dir(&out_path).expect("Could not create assets dir"); + } - let context = &*PROV; + let context = &*PROV; - std::fs::write( - std::path::Path::new(&format!( - "{}/context.json", - out_path.as_os_str().to_string_lossy(), - )), - serde_json::to_string_pretty(context)?, - )?; + std::fs::write( + std::path::Path::new(&format!("{}/context.json", out_path.as_os_str().to_string_lossy(),)), + serde_json::to_string_pretty(context)?, + )?; - Ok(()) + Ok(()) } diff --git a/crates/common/src/attributes.rs b/crates/common/src/attributes.rs index 6eb77e3b8..b7e67c892 100644 --- a/crates/common/src/attributes.rs +++ b/crates/common/src/attributes.rs @@ -1,112 +1,113 @@ +#[cfg(feature = "std")] +use std::collections::BTreeMap; + +#[cfg(not(feature = "std"))] +use parity_scale_codec::{alloc::collections::BTreeMap, alloc::string::String}; +#[cfg(not(feature = "std"))] +use scale_info::{prelude::borrow::ToOwned, prelude::string::ToString, prelude::*}; + use parity_scale_codec::{Decode, Encode}; use scale_info::{build::Fields, Path, Type, TypeInfo}; use serde_json::Value; -use std::collections::BTreeMap; use crate::prov::DomaintypeId; #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] pub struct SerdeWrapper(pub Value); -impl std::fmt::Display for SerdeWrapper { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match serde_json::to_string(&self.0) { - Ok(json_string) => write!(f, "{}", json_string), - Err(e) => { - tracing::error!("Failed to serialize Value to JSON string: {}", e); - Err(std::fmt::Error) - } - } - } +impl core::fmt::Display for SerdeWrapper { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match serde_json::to_string(&self.0) { + Ok(json_string) => write!(f, "{}", json_string), + Err(e) => { + tracing::error!("Failed to serialize Value to JSON string: {}", e); + Err(core::fmt::Error) + }, + } + } } impl Encode for SerdeWrapper { - fn encode_to(&self, dest: &mut T) { - let json_string = - serde_json::to_string(&self.0).expect("Failed to serialize Value to JSON string"); - json_string.encode_to(dest); - } + fn encode_to(&self, dest: &mut T) { + let json_string = + serde_json::to_string(&self.0).expect("Failed to serialize Value to JSON string"); + json_string.encode_to(dest); + } } impl From for SerdeWrapper { - fn from(value: Value) -> Self { - SerdeWrapper(value) - } + fn from(value: Value) -> Self { + SerdeWrapper(value) + } } impl From for Value { - fn from(wrapper: SerdeWrapper) -> Self { - wrapper.0 - } + fn from(wrapper: SerdeWrapper) -> Self { + wrapper.0 + } } impl Decode for SerdeWrapper { - fn decode( - input: &mut I, - ) -> Result { - let json_string = String::decode(input)?; - let value = serde_json::from_str(&json_string).map_err(|_| { - parity_scale_codec::Error::from("Failed to deserialize JSON string to Value") - })?; - Ok(SerdeWrapper(value)) - } + fn decode( + input: &mut I, + ) -> Result { + let json_string = String::decode(input)?; + let value = serde_json::from_str(&json_string).map_err(|_| { + parity_scale_codec::Error::from("Failed to deserialize JSON string to Value") + })?; + Ok(SerdeWrapper(value)) + } } impl TypeInfo for SerdeWrapper { - type Identity = Self; - fn type_info() -> Type { - Type::builder() - .path(Path::new("SerdeWrapper", module_path!())) - .composite(Fields::unnamed().field(|f| f.ty::().type_name("Json"))) - } + type Identity = Self; + fn type_info() -> Type { + Type::builder() + .path(Path::new("SerdeWrapper", module_path!())) + .composite(Fields::unnamed().field(|f| f.ty::().type_name("Json"))) + } } #[derive(Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, PartialEq, Eq)] pub struct Attribute { - pub typ: String, - pub value: SerdeWrapper, + pub typ: String, + pub value: SerdeWrapper, } -impl std::fmt::Display for Attribute { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Type: {}, Value: {}", - self.typ, - serde_json::to_string(&self.value.0).unwrap_or_else(|_| String::from("Invalid Value")) - ) - } +impl core::fmt::Display for Attribute { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "Type: {}, Value: {}", + self.typ, + serde_json::to_string(&self.value.0).unwrap_or_else(|_| String::from("Invalid Value")) + ) + } } impl Attribute { - pub fn get_type(&self) -> &String { - &self.typ - } - - pub fn get_value(&self) -> &Value { - &self.value.0 - } - pub fn new(typ: impl AsRef, value: Value) -> Self { - Self { - typ: typ.as_ref().to_owned(), - value: value.into(), - } - } + pub fn get_type(&self) -> &String { + &self.typ + } + + pub fn get_value(&self) -> &Value { + &self.value.0 + } + pub fn new(typ: impl AsRef, value: Value) -> Self { + Self { typ: typ.as_ref().to_owned(), value: value.into() } + } } #[derive( - Debug, Clone, Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Default, + Debug, Clone, Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Default, )] pub struct Attributes { - pub typ: Option, - pub attributes: BTreeMap, + pub typ: Option, + pub attributes: BTreeMap, } impl Attributes { - pub fn type_only(typ: Option) -> Self { - Self { - typ, - attributes: BTreeMap::new(), - } - } + pub fn type_only(typ: Option) -> Self { + Self { typ, attributes: BTreeMap::new() } + } } diff --git a/crates/common/src/commands.rs b/crates/common/src/commands.rs deleted file mode 100644 index 7b315b53a..000000000 --- a/crates/common/src/commands.rs +++ /dev/null @@ -1,350 +0,0 @@ -use std::{path::PathBuf, pin::Pin, sync::Arc}; - -use chrono::{DateTime, Utc}; -use derivative::*; -use futures::AsyncRead; - -use serde::{Deserialize, Serialize}; - -use crate::{ - attributes::Attributes, - prov::{ - operations::{ChronicleOperation, DerivationType}, - ActivityId, AgentId, ChronicleIri, ChronicleTransactionId, EntityId, ExternalId, - NamespaceId, ProvModel, Role, - }, -}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum NamespaceCommand { - Create { external_id: ExternalId }, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum AgentCommand { - Create { - external_id: ExternalId, - namespace: ExternalId, - attributes: Attributes, - }, - UseInContext { - id: AgentId, - namespace: ExternalId, - }, - Delegate { - id: AgentId, - delegate: AgentId, - activity: Option, - namespace: ExternalId, - role: Option, - }, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ActivityCommand { - Create { - external_id: ExternalId, - namespace: ExternalId, - attributes: Attributes, - }, - Instant { - id: ActivityId, - namespace: ExternalId, - time: Option>, - agent: Option, - }, - Start { - id: ActivityId, - namespace: ExternalId, - time: Option>, - agent: Option, - }, - End { - id: ActivityId, - namespace: ExternalId, - time: Option>, - agent: Option, - }, - Use { - id: EntityId, - namespace: ExternalId, - activity: ActivityId, - }, - Generate { - id: EntityId, - namespace: ExternalId, - activity: ActivityId, - }, - WasInformedBy { - id: ActivityId, - namespace: ExternalId, - informing_activity: ActivityId, - }, - Associate { - id: ActivityId, - namespace: ExternalId, - responsible: AgentId, - role: Option, - }, -} - -impl ActivityCommand { - pub fn create( - external_id: impl AsRef, - namespace: impl AsRef, - attributes: Attributes, - ) -> Self { - Self::Create { - external_id: external_id.as_ref().into(), - namespace: namespace.as_ref().into(), - attributes, - } - } - - pub fn start( - id: ActivityId, - namespace: impl AsRef, - time: Option>, - agent: Option, - ) -> Self { - Self::Start { - id, - namespace: namespace.as_ref().into(), - time, - agent, - } - } - - pub fn end( - id: ActivityId, - namespace: impl AsRef, - time: Option>, - agent: Option, - ) -> Self { - Self::End { - id, - namespace: namespace.as_ref().into(), - time, - agent, - } - } - - pub fn instant( - id: ActivityId, - namespace: impl AsRef, - time: Option>, - agent: Option, - ) -> Self { - Self::End { - id, - namespace: namespace.as_ref().into(), - time, - agent, - } - } - - pub fn r#use(id: EntityId, namespace: impl AsRef, activity: ActivityId) -> Self { - Self::Use { - id, - namespace: namespace.as_ref().into(), - activity, - } - } - - pub fn was_informed_by( - id: ActivityId, - namespace: impl AsRef, - informing_activity: ActivityId, - ) -> Self { - Self::WasInformedBy { - id, - namespace: namespace.as_ref().into(), - informing_activity, - } - } - - pub fn generate(id: EntityId, namespace: impl AsRef, activity: ActivityId) -> Self { - Self::Generate { - id, - namespace: namespace.as_ref().into(), - activity, - } - } -} - -#[derive(Derivative)] -#[derivative(Debug, Clone)] -pub enum PathOrFile { - Path(PathBuf), - File(#[derivative(Debug = "ignore")] Arc>>), //Non serialisable variant, used in process -} - -impl Serialize for PathOrFile { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - PathOrFile::Path(path) => path.serialize(serializer), - _ => { - unreachable!() - } - } - } -} - -impl<'de> Deserialize<'de> for PathOrFile { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ok(PathOrFile::Path(PathBuf::deserialize(deserializer)?)) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum EntityCommand { - Create { - external_id: ExternalId, - namespace: ExternalId, - attributes: Attributes, - }, - Attribute { - id: EntityId, - namespace: ExternalId, - responsible: AgentId, - role: Option, - }, - Derive { - id: EntityId, - namespace: ExternalId, - derivation: DerivationType, - activity: Option, - used_entity: EntityId, - }, -} - -impl EntityCommand { - pub fn create( - external_id: impl AsRef, - namespace: impl AsRef, - attributes: Attributes, - ) -> Self { - Self::Create { - external_id: external_id.as_ref().into(), - namespace: namespace.as_ref().into(), - attributes, - } - } - - pub fn detach( - id: EntityId, - namespace: impl AsRef, - derivation: DerivationType, - activity: Option, - used_entity: EntityId, - ) -> Self { - Self::Derive { - id, - namespace: namespace.as_ref().into(), - derivation, - activity, - used_entity, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct QueryCommand { - pub namespace: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DepthChargeCommand { - pub namespace: NamespaceId, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ImportCommand { - pub namespace: NamespaceId, - pub operations: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ApiCommand { - NameSpace(NamespaceCommand), - Agent(AgentCommand), - Activity(ActivityCommand), - Entity(EntityCommand), - Query(QueryCommand), - DepthCharge(DepthChargeCommand), - Import(ImportCommand), -} - -#[derive(Debug)] -pub enum ApiResponse { - /// The api has successfully executed the operation, but has no useful output - Unit, - /// The operation will not result in any data changes - AlreadyRecorded { - subject: ChronicleIri, - prov: Box, - }, - /// The api has validated the command and submitted a transaction to a ledger - Submission { - subject: ChronicleIri, - prov: Box, - tx_id: ChronicleTransactionId, - }, - /// The api has successfully executed the query - QueryReply { prov: Box }, - /// The api has submitted the import transactions to a ledger - ImportSubmitted { - prov: Box, - tx_id: ChronicleTransactionId, - }, - /// The api has submitted the depth charge transaction to a ledger - DepthChargeSubmitted { tx_id: ChronicleTransactionId }, -} - -impl ApiResponse { - pub fn submission( - subject: impl Into, - prov: ProvModel, - tx_id: ChronicleTransactionId, - ) -> Self { - ApiResponse::Submission { - subject: subject.into(), - prov: Box::new(prov), - tx_id, - } - } - - pub fn unit() -> Self { - ApiResponse::Unit - } - - pub fn query_reply(prov: ProvModel) -> Self { - ApiResponse::QueryReply { - prov: Box::new(prov), - } - } - - pub fn already_recorded(subject: impl Into, prov: ProvModel) -> Self { - ApiResponse::AlreadyRecorded { - subject: subject.into(), - prov: Box::new(prov), - } - } - - pub fn depth_charge_submission(tx_id: ChronicleTransactionId) -> Self { - ApiResponse::DepthChargeSubmitted { tx_id } - } - - pub fn import_submitted(prov: ProvModel, tx_id: ChronicleTransactionId) -> Self { - ApiResponse::ImportSubmitted { - prov: Box::new(prov), - tx_id, - } - } -} diff --git a/crates/common/src/context.rs b/crates/common/src/context.rs index 9624e2d27..9c68120a8 100644 --- a/crates/common/src/context.rs +++ b/crates/common/src/context.rs @@ -2,117 +2,117 @@ use lazy_static::lazy_static; use serde_json::{json, Value}; lazy_static! { - pub static ref PROV: Value = json!({ - "@version": 1.1, - "prov": "http://www.w3.org/ns/prov#", - "provext": "https://openprovenance.org/ns/provext#", - "xsd": "http://www.w3.org/2001/XMLSchema#", - "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", - "chronicle":"http://btp.works/chronicle/ns#", - "chronicle_op":"http://btp.works/chronicleoperations/ns#", - "entity": { - "@id": "prov:entity", - "@type": "@id" - }, - - "activity": { - "@id": "prov:activity", - "@type": "@id" - }, - - "agent": { - "@id": "prov:agent", - "@type": "@id" - }, - - "externalId" : { - "@id": "chronicle:externalId", - }, - - "namespace": { - "@id": "chronicle:hasNamespace", - "@type": "@id" - }, - - "publicKey": { - "@id": "chronicle:hasPublicKey", - }, - - "identity": { - "@id": "chronicle:hasIdentity", - "@type" : "@id", - }, - - "previousIdentities": { - "@id": "chronicle:hadIdentity", - "@type" : "@id", - "@container": "@set" - }, - - "wasAssociatedWith": { - "@id": "prov:wasAssociatedWith", - "@type" : "@id", - "@container": "@set" - }, - - "wasAttributedTo": { - "@id": "prov:wasAttributedTo", - "@type" : "@id", - "@container": "@set" - }, - - "wasDerivedFrom": { - "@id": "prov:wasDerivedFrom", - "@type" : "@id", - "@container": "@set" - }, - - "hadPrimarySource": { - "@id": "prov:hadPrimarySource", - "@type" : "@id", - "@container": "@set" - }, - - "actedOnBehalfOf": { - "@id": "prov:actedOnBehalfOf", - "@type" : "@id", - "@container": "@set" - }, - - "wasQuotedFrom": { - "@id": "prov:wasQuotedFrom", - "@type" : "@id", - "@container": "@set" - }, - - "wasRevisionOf": { - "@id": "prov:wasRevisionOf", - "@type" : "@id", - "@container": "@set" - }, - - "used": { - "@id": "prov:used", - "@type" : "@id", - "@container": "@set" - }, - - "wasGeneratedBy": { - "@id": "prov:wasGeneratedBy", - "@type" : "@id", - "@container": "@set" - }, - - "startTime": { - "@id": "prov:startedAtTime", - }, - - "endTime": { - "@id": "prov:endedAtTime", - }, - "value": { - "@id": "chronicle:value", - "@type" : "@json", - }, - }); + pub static ref PROV: Value = json!({ + "@version": 1.1, + "prov": "http://www.w3.org/ns/prov#", + "provext": "https://openprovenance.org/ns/provext#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "chronicle":"http://btp.works/chronicle/ns#", + "chronicle_op":"http://btp.works/chronicleoperations/ns#", + "entity": { + "@id": "prov:entity", + "@type": "@id" + }, + + "activity": { + "@id": "prov:activity", + "@type": "@id" + }, + + "agent": { + "@id": "prov:agent", + "@type": "@id" + }, + + "externalId" : { + "@id": "chronicle:externalId", + }, + + "namespace": { + "@id": "chronicle:hasNamespace", + "@type": "@id" + }, + + "publicKey": { + "@id": "chronicle:hasPublicKey", + }, + + "identity": { + "@id": "chronicle:hasIdentity", + "@type" : "@id", + }, + + "previousIdentities": { + "@id": "chronicle:hadIdentity", + "@type" : "@id", + "@container": "@set" + }, + + "wasAssociatedWith": { + "@id": "prov:wasAssociatedWith", + "@type" : "@id", + "@container": "@set" + }, + + "wasAttributedTo": { + "@id": "prov:wasAttributedTo", + "@type" : "@id", + "@container": "@set" + }, + + "wasDerivedFrom": { + "@id": "prov:wasDerivedFrom", + "@type" : "@id", + "@container": "@set" + }, + + "hadPrimarySource": { + "@id": "prov:hadPrimarySource", + "@type" : "@id", + "@container": "@set" + }, + + "actedOnBehalfOf": { + "@id": "prov:actedOnBehalfOf", + "@type" : "@id", + "@container": "@set" + }, + + "wasQuotedFrom": { + "@id": "prov:wasQuotedFrom", + "@type" : "@id", + "@container": "@set" + }, + + "wasRevisionOf": { + "@id": "prov:wasRevisionOf", + "@type" : "@id", + "@container": "@set" + }, + + "used": { + "@id": "prov:used", + "@type" : "@id", + "@container": "@set" + }, + + "wasGeneratedBy": { + "@id": "prov:wasGeneratedBy", + "@type" : "@id", + "@container": "@set" + }, + + "startTime": { + "@id": "prov:startedAtTime", + }, + + "endTime": { + "@id": "prov:endedAtTime", + }, + "value": { + "@id": "chronicle:value", + "@type" : "@json", + }, + }); } diff --git a/crates/common/src/database.rs b/crates/common/src/database.rs deleted file mode 100644 index 4a4b8f2a4..000000000 --- a/crates/common/src/database.rs +++ /dev/null @@ -1,72 +0,0 @@ -use diesel::{r2d2::ConnectionManager, Connection, PgConnection}; -use lazy_static::lazy_static; -use r2d2::Pool; -use std::{fmt::Display, time::Duration}; -use testcontainers::{clients::Cli, images::postgres::Postgres, Container}; - -lazy_static! { - static ref CLIENT: Cli = Cli::default(); -} - -pub struct TemporaryDatabase<'a> { - db_uris: Vec, - _container: Container<'a, Postgres>, -} - -impl<'a> TemporaryDatabase<'a> { - pub fn connection_pool(&self) -> Result>, r2d2::Error> { - let db_uri = self - .db_uris - .iter() - .find(|db_uri| PgConnection::establish(db_uri).is_ok()) - .expect("cannot establish connection"); - Pool::builder().build(ConnectionManager::::new(db_uri)) - } -} - -impl<'a> Default for TemporaryDatabase<'a> { - fn default() -> Self { - let container = CLIENT.run(Postgres::default()); - const PORT: u16 = 5432; - Self { - db_uris: vec![ - format!( - "postgresql://postgres@127.0.0.1:{}/", - container.get_host_port_ipv4(PORT) - ), - format!( - "postgresql://postgres@{}:{}/", - container.get_bridge_ip_address(), - PORT - ), - ], - _container: container, - } - } -} - -#[async_trait::async_trait] -pub trait DatabaseConnector { - async fn try_connect(&self) -> Result<(X, Pool>), E>; - fn should_retry(&self, error: &E) -> bool; -} - -pub async fn get_connection_with_retry( - connector: impl DatabaseConnector, -) -> Result<(X, Pool>), E> { - let mut i = 1; - let mut j = 1; - loop { - let connection = connector.try_connect().await; - if let Err(source) = &connection { - tracing::warn!("database connection failed: {source}"); - if i < 20 && connector.should_retry(source) { - tracing::info!("waiting to retry database connection..."); - std::thread::sleep(Duration::from_secs(i)); - (i, j) = (i + j, i); - continue; - } - } - return connection; - } -} diff --git a/crates/common/src/identity.rs b/crates/common/src/identity.rs index 5a71b4acd..9147603fd 100644 --- a/crates/common/src/identity.rs +++ b/crates/common/src/identity.rs @@ -1,297 +1,276 @@ -use std::{collections::BTreeSet, fmt}; - use crate::prov::AgentId; -use chronicle_signing::{ChronicleKnownKeyNamesSigner, SecretError}; use k256::{ - ecdsa::VerifyingKey, - sha2::{Digest, Sha512}, + ecdsa::VerifyingKey, + sha2::{Digest, Sha512}, }; use serde_json::{Map, Value}; -use thiserror::Error; use tracing::warn; +#[cfg(not(feature = "std"))] +use parity_scale_codec::{ + alloc::collections::BTreeMap, alloc::collections::BTreeSet, alloc::string::String, + alloc::vec::Vec, +}; +#[cfg(not(feature = "std"))] +use scale_info::{prelude::borrow::ToOwned, prelude::string::ToString, prelude::*}; +#[cfg(feature = "std")] +use std::collections::BTreeMap; + +#[cfg(feature = "std")] +use thiserror::Error; +#[cfg(not(feature = "std"))] +use thiserror_no_std::Error; + +#[cfg(feature = "std")] +use std::collections::BTreeSet; + #[derive(Error, Debug)] pub enum IdentityError { - #[error("Failed to get agent id from JWT claims")] - JwtClaims, + #[error("Failed to get agent id from JWT claims")] + JwtClaims, - #[error("Signer : {0}")] - KeyStore(#[from] SecretError), + #[error("Signer : {0}")] + KeyStore(#[from] anyhow::Error), - #[error("Malformed JSON: {0}")] - SerdeJson(#[from] serde_json::Error), + #[error("Malformed JSON: {0}")] + SerdeJson(#[from] serde_json::Error), - #[error("Serialization error: {0}")] - SerdeJsonSerialize(String), + #[error("Serialization error: {0}")] + SerdeJsonSerialize(String), - #[error("Signing error: {0}")] - Signing(#[from] k256::ecdsa::Error), + #[error("Signing error: {0}")] + Signing(#[from] k256::ecdsa::Error), } /// Contains the scalar ID and identity claims for a user established via JWT #[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct JwtId { - pub id: AgentId, - pub claims: Value, + pub id: AgentId, + pub claims: Value, } impl JwtId { - fn new(external_id: &str, claims: Value) -> Self { - Self { - id: AgentId::from_external_id(external_id), - claims, - } - } + fn new(external_id: &str, claims: Value) -> Self { + Self { id: AgentId::from_external_id(external_id), claims } + } } -impl std::fmt::Debug for JwtId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.debug_struct("JwtId") - .field("id", &self.id) - .finish_non_exhaustive() - } +impl core::fmt::Debug for JwtId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + f.debug_struct("JwtId").field("id", &self.id).finish_non_exhaustive() + } } /// Claims from a JWT, referenced in creating an AgentId for a Chronicle user #[derive(Clone, Deserialize, Serialize)] pub struct JwtClaims(pub Map); -impl std::fmt::Debug for JwtClaims { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - let claims = self - .0 - .iter() - .map(|(k, _v)| (k, "***SECRET***")) - .collect::>(); - write!(f, "JwtClaims({:?})", claims) - } +impl core::fmt::Debug for JwtClaims { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + let claims = self.0.iter().map(|(k, _v)| (k, "***SECRET***")).collect::>(); + write!(f, "JwtClaims({:?})", claims) + } } /// Chronicle identity object for authorization #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] #[serde(rename_all = "lowercase", tag = "type")] pub enum AuthId { - Anonymous, - Chronicle, - JWT(JwtId), + Anonymous, + Chronicle, + JWT(JwtId), } -impl fmt::Display for AuthId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Anonymous => write!(f, "Anonymous"), - Self::Chronicle => write!(f, "Chronicle"), - Self::JWT(jwt_id) => write!(f, "{}", jwt_id.id), - } - } +impl core::fmt::Display for AuthId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Anonymous => write!(f, "Anonymous"), + Self::Chronicle => write!(f, "Chronicle"), + Self::JWT(jwt_id) => write!(f, "{}", jwt_id.id), + } + } } impl TryFrom<&str> for AuthId { - type Error = serde_json::Error; + type Error = serde_json::Error; - fn try_from(s: &str) -> Result { - serde_json::from_str(s) - } + fn try_from(s: &str) -> Result { + serde_json::from_str(s) + } } impl AuthId { - /// Establish a Chronicle user via JWT using a provided pointer into the JWT claims, - /// caching the claims with the JWT user identity - pub fn from_jwt_claims( - JwtClaims(claims): &JwtClaims, - id_keys: &BTreeSet, - ) -> Result { - const ZERO: [u8; 1] = [0]; - - let mut hasher = Sha512::new(); - - for id_key in id_keys { - if let Some(Value::String(claim_value)) = claims.get(id_key) { - hasher.update(id_key.as_bytes()); - hasher.update(ZERO); - hasher.update(claim_value.as_bytes()); - hasher.update(ZERO); - } else { - let keys_available: Vec<&String> = claims.keys().collect(); - warn!( + /// Establish a Chronicle user via JWT using a provided pointer into the JWT claims, + /// caching the claims with the JWT user identity + pub fn from_jwt_claims( + JwtClaims(claims): &JwtClaims, + id_keys: &BTreeSet, + ) -> Result { + const ZERO: [u8; 1] = [0]; + + let mut hasher = Sha512::new(); + + for id_key in id_keys { + if let Some(Value::String(claim_value)) = claims.get(id_key) { + hasher.update(id_key.as_bytes()); + hasher.update(ZERO); + hasher.update(claim_value.as_bytes()); + hasher.update(ZERO); + } else { + let keys_available: Vec<&String> = claims.keys().collect(); + warn!( "For constructing Chronicle identity no {id_key:?} field among JWT claims: {keys_available:?}" ); - return Err(IdentityError::JwtClaims); - } - } - - Ok(Self::JWT(JwtId::new( - &hex::encode(hasher.finalize()), - Value::Object(claims.to_owned()), - ))) - } - - /// Create an Anonymous Chronicle user - pub fn anonymous() -> Self { - Self::Anonymous - } - - /// Create a Chronicle super user - pub fn chronicle() -> Self { - Self::Chronicle - } - - /// Serialize identity to a JSON object containing "type" ("Anonymous", "Chronicle", or "JWT"), - /// and, in the case of a JWT identity, "id" fields - the Input for an OPA check - pub fn identity(&self) -> Result { - serde_json::to_value(self).map_err(|e| IdentityError::SerdeJsonSerialize(e.to_string())) - } - - /// Get the user identity's [`SignedIdentity`] - pub fn signed_identity( - &self, - store: &S, - ) -> Result { - let buf = serde_json::to_string(self)?.as_bytes().to_vec(); - - futures::executor::block_on(async move { - SignedIdentity::new( - self, - store.chronicle_sign(&buf).await?, - store.chronicle_verifying().await?, - ) - }) - } + return Err(IdentityError::JwtClaims); + } + } + + Ok(Self::JWT(JwtId::new(&hex::encode(hasher.finalize()), Value::Object(claims.to_owned())))) + } + + /// Create an Anonymous Chronicle user + pub fn anonymous() -> Self { + Self::Anonymous + } + + /// Create a Chronicle super user + pub fn chronicle() -> Self { + Self::Chronicle + } + + /// Serialize identity to a JSON object containing "type" ("Anonymous", "Chronicle", or "JWT"), + /// and, in the case of a JWT identity, "id" fields - the Input for an OPA check + pub fn identity(&self) -> Result { + serde_json::to_value(self).map_err(|e| IdentityError::SerdeJsonSerialize(e.to_string())) + } } /// Context data for an OPA check - `operation` and `state` fields are /// equivalent to GraphQL parent type and path node #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] struct Context { - operation: Value, - state: Value, + operation: Value, + state: Value, } /// Identity and Context data for an OPA check #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] pub struct IdentityContext { - identity: AuthId, - context: Context, + identity: AuthId, + context: Context, } impl IdentityContext { - pub fn new(identity: AuthId, operation: Value, state: Value) -> Self { - Self { - identity, - context: Context { operation, state }, - } - } + pub fn new(identity: AuthId, operation: Value, state: Value) -> Self { + Self { identity, context: Context { operation, state } } + } } /// Contextual data for OPA created either via GraphQL or in the Transaction Processor #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] #[serde(rename_all = "lowercase", tag = "type")] pub enum OpaData { - GraphQL(IdentityContext), - Operation(IdentityContext), + GraphQL(IdentityContext), + Operation(IdentityContext), } impl OpaData { - pub fn graphql(identity: &AuthId, parent_type: &Value, resolve_path: &Value) -> Self { - Self::GraphQL(IdentityContext::new( - identity.to_owned(), - parent_type.to_owned(), - resolve_path.to_owned(), - )) - } - - pub fn operation(identity: &AuthId, operation: &Value, state: &Value) -> Self { - Self::Operation(IdentityContext::new( - identity.to_owned(), - operation.to_owned(), - state.to_owned(), - )) - } + pub fn graphql(identity: &AuthId, parent_type: &Value, resolve_path: &Value) -> Self { + Self::GraphQL(IdentityContext::new( + identity.to_owned(), + parent_type.to_owned(), + resolve_path.to_owned(), + )) + } + + pub fn operation(identity: &AuthId, operation: &Value, state: &Value) -> Self { + Self::Operation(IdentityContext::new( + identity.to_owned(), + operation.to_owned(), + state.to_owned(), + )) + } } /// Signed user identity containing the serialized identity, signature, and /// verifying key. Implements `TryFrom` to deserialize to the user identity object -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct SignedIdentity { - pub identity: String, - pub signature: Option>, - pub verifying_key: Option, + pub identity: String, + pub signature: Option>, + pub verifying_key: Option, } impl SignedIdentity { - fn new( - id: &AuthId, - signature: Vec, - verifying_key: VerifyingKey, - ) -> Result { - Ok(Self { - identity: serde_json::to_string(&id)?, - signature: Some(signature), - verifying_key: Some(verifying_key), - }) - } - - pub fn new_no_identity() -> Self { - Self { - identity: "none".to_string(), - signature: None, - verifying_key: None, - } - } + fn new( + id: &AuthId, + signature: Vec, + verifying_key: VerifyingKey, + ) -> Result { + Ok(Self { + identity: serde_json::to_string(&id)?, + signature: Some(signature), + verifying_key: Some(verifying_key), + }) + } + + pub fn new_no_identity() -> Self { + Self { identity: "none".to_string(), signature: None, verifying_key: None } + } } impl TryFrom<&SignedIdentity> for AuthId { - type Error = serde_json::Error; + type Error = serde_json::Error; - fn try_from(signed_identity: &SignedIdentity) -> Result { - serde_json::from_str(&signed_identity.identity) - } + fn try_from(signed_identity: &SignedIdentity) -> Result { + serde_json::from_str(&signed_identity.identity) + } } #[cfg(test)] mod tests { - use super::*; - use crate::prov::{ExternalId, ExternalIdPart}; - use serde_json::json; - - fn external_id_from_jwt_claims<'a>(claim_strings: impl Iterator) -> ExternalId { - const ZERO: [u8; 1] = [0]; - let mut hasher = Sha512::new(); - claim_strings.for_each(|s| { - hasher.update(s.as_bytes()); - hasher.update(ZERO); - }); - hex::encode(hasher.finalize()).into() - } - - #[test] - fn test_auth_id_serialization() { - let auth_id = AuthId::anonymous(); - insta::assert_json_snapshot!(auth_id, @r###" + use super::*; + use crate::prov::{ExternalId, ExternalIdPart}; + use serde_json::json; + + fn external_id_from_jwt_claims<'a>(claim_strings: impl Iterator) -> ExternalId { + const ZERO: [u8; 1] = [0]; + let mut hasher = Sha512::new(); + claim_strings.for_each(|s| { + hasher.update(s.as_bytes()); + hasher.update(ZERO); + }); + hex::encode(hasher.finalize()).into() + } + + #[test] + fn test_auth_id_serialization() { + let auth_id = AuthId::anonymous(); + insta::assert_json_snapshot!(auth_id, @r###" { "type": "anonymous" } "###); - let auth_id = AuthId::chronicle(); - insta::assert_json_snapshot!(auth_id, @r###" + let auth_id = AuthId::chronicle(); + insta::assert_json_snapshot!(auth_id, @r###" { "type": "chronicle" } "###); - let claims = JwtClaims( - json!({ - "name": "abcdef", - }) - .as_object() - .unwrap() - .to_owned(), - ); - let auth_id = - AuthId::from_jwt_claims(&claims, &BTreeSet::from(["name".to_string()])).unwrap(); - insta::assert_json_snapshot!(auth_id, @r###" + let claims = JwtClaims( + json!({ + "name": "abcdef", + }) + .as_object() + .unwrap() + .to_owned(), + ); + let auth_id = + AuthId::from_jwt_claims(&claims, &BTreeSet::from(["name".to_string()])).unwrap(); + insta::assert_json_snapshot!(auth_id, @r###" { "type": "jwt", "id": "6e7f57aeab5edb9bf5863ba2d749715b6f9a9079f3b8c81b6207d437c005b5b9f6f14de53c34c38ee0b1cc77fa6e02b5cef694faf5aaf028b58c15b3c4ee1cb0", @@ -301,59 +280,59 @@ mod tests { } "###); - if let AuthId::JWT(JwtId { id, .. }) = auth_id { - assert_eq!( - &external_id_from_jwt_claims(vec!["name", "abcdef"].into_iter()), - id.external_id_part() - ); - } else { - panic!("did not receive expected JWT identity: {auth_id}"); - } - } - - #[test] - fn test_auth_id_deserialization() { - let serialized = r#"{"type":"anonymous"}"#; - let deserialized: AuthId = serde_json::from_str(serialized).unwrap(); - assert_eq!(deserialized, AuthId::Anonymous); - - let serialized = r#"{"type":"chronicle"}"#; - let deserialized: AuthId = serde_json::from_str(serialized).unwrap(); - assert_eq!(deserialized, AuthId::Chronicle); - - let serialized = r#"{ + if let AuthId::JWT(JwtId { id, .. }) = auth_id { + assert_eq!( + &external_id_from_jwt_claims(vec!["name", "abcdef"].into_iter()), + id.external_id_part() + ); + } else { + panic!("did not receive expected JWT identity: {auth_id}"); + } + } + + #[test] + fn test_auth_id_deserialization() { + let serialized = r#"{"type":"anonymous"}"#; + let deserialized: AuthId = serde_json::from_str(serialized).unwrap(); + assert_eq!(deserialized, AuthId::Anonymous); + + let serialized = r#"{"type":"chronicle"}"#; + let deserialized: AuthId = serde_json::from_str(serialized).unwrap(); + assert_eq!(deserialized, AuthId::Chronicle); + + let serialized = r#"{ "type": "jwt", "id": "abcdef", "claims": { "name": "abcdef" } }"#; - let deserialized: AuthId = serde_json::from_str(serialized).unwrap(); - assert_eq!( - deserialized, - AuthId::JWT(JwtId { - id: AgentId::from_external_id("abcdef"), - claims: json!({ - "name": "abcdef" - }) - }) - ); - } - - #[test] - fn test_auth_id_from_jwt_claims() { - let claims = JwtClaims( - json!({ - "sub": "John Doe" - }) - .as_object() - .unwrap() - .to_owned(), - ); - let auth_id = - AuthId::from_jwt_claims(&claims, &BTreeSet::from(["sub".to_string()])).unwrap(); - - insta::assert_json_snapshot!(auth_id, @r###" + let deserialized: AuthId = serde_json::from_str(serialized).unwrap(); + assert_eq!( + deserialized, + AuthId::JWT(JwtId { + id: AgentId::from_external_id("abcdef"), + claims: json!({ + "name": "abcdef" + }) + }) + ); + } + + #[test] + fn test_auth_id_from_jwt_claims() { + let claims = JwtClaims( + json!({ + "sub": "John Doe" + }) + .as_object() + .unwrap() + .to_owned(), + ); + let auth_id = + AuthId::from_jwt_claims(&claims, &BTreeSet::from(["sub".to_string()])).unwrap(); + + insta::assert_json_snapshot!(auth_id, @r###" { "type": "jwt", "id": "13cc0854e3c226984a47e3159be9d71dae9796586ae15c493a7dcb79c2c511be7b311a238439a6922b779014b2bc71f351ff388fcac012d4f20f161720fa0dcf", @@ -363,50 +342,47 @@ mod tests { } "###); - if let AuthId::JWT(JwtId { id, .. }) = auth_id { - assert_eq!( - &external_id_from_jwt_claims(vec!["sub", "John Doe"].into_iter()), - id.external_id_part() - ); - } else { - panic!("did not receive expected JWT identity: {auth_id}"); - } - } - - #[test] - fn test_auth_id_from_jwt_claims_failure() { - let claims = JwtClaims( - json!({ - "sub": "John Doe" - }) - .as_object() - .unwrap() - .to_owned(), - ); - let auth_id_result = - AuthId::from_jwt_claims(&claims, &BTreeSet::from(["externalId".to_string()])); - assert!(auth_id_result.is_err()); - assert_eq!( - auth_id_result.unwrap_err().to_string(), - IdentityError::JwtClaims.to_string() - ); - } - - #[test] - fn test_opa_data_serialization() { - let identity = AuthId::Chronicle; - let operation = json!({ - "resource": "users", - "action": "read" - }); - let state = json!([{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]); - let context = OpaData::graphql(&identity, &operation, &state); - - let json = serde_json::to_string(&context).unwrap(); - let deserialized_context: OpaData = serde_json::from_str(&json).unwrap(); - - assert!(context == deserialized_context); - insta::assert_json_snapshot!(context, @r###" + if let AuthId::JWT(JwtId { id, .. }) = auth_id { + assert_eq!( + &external_id_from_jwt_claims(vec!["sub", "John Doe"].into_iter()), + id.external_id_part() + ); + } else { + panic!("did not receive expected JWT identity: {auth_id}"); + } + } + + #[test] + fn test_auth_id_from_jwt_claims_failure() { + let claims = JwtClaims( + json!({ + "sub": "John Doe" + }) + .as_object() + .unwrap() + .to_owned(), + ); + let auth_id_result = + AuthId::from_jwt_claims(&claims, &BTreeSet::from(["externalId".to_string()])); + assert!(auth_id_result.is_err()); + assert_eq!(auth_id_result.unwrap_err().to_string(), IdentityError::JwtClaims.to_string()); + } + + #[test] + fn test_opa_data_serialization() { + let identity = AuthId::Chronicle; + let operation = json!({ + "resource": "users", + "action": "read" + }); + let state = json!([{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]); + let context = OpaData::graphql(&identity, &operation, &state); + + let json = serde_json::to_string(&context).unwrap(); + let deserialized_context: OpaData = serde_json::from_str(&json).unwrap(); + + assert!(context == deserialized_context); + insta::assert_json_snapshot!(context, @r###" { "type": "graphql", "identity": { @@ -430,30 +406,30 @@ mod tests { } } "###); - } - - #[test] - fn test_jwt_claims_custom_debug() { - let claims = JwtClaims( - json!({ - "key": "value", - }) - .as_object() - .unwrap() - .to_owned(), - ); - insta::assert_debug_snapshot!(claims, @r###"JwtClaims({"key": "***SECRET***"})"###); - } - - #[test] - fn test_jwt_id_custom_debug() { - let jwt_id = AuthId::JWT(JwtId { - id: AgentId::from_external_id("abcdef"), - claims: json!({ - "key": "value" - }), - }); - insta::assert_debug_snapshot!(jwt_id, @r###" + } + + #[test] + fn test_jwt_claims_custom_debug() { + let claims = JwtClaims( + json!({ + "key": "value", + }) + .as_object() + .unwrap() + .to_owned(), + ); + insta::assert_debug_snapshot!(claims, @r###"JwtClaims({"key": "***SECRET***"})"###); + } + + #[test] + fn test_jwt_id_custom_debug() { + let jwt_id = AuthId::JWT(JwtId { + id: AgentId::from_external_id("abcdef"), + claims: json!({ + "key": "value" + }), + }); + insta::assert_debug_snapshot!(jwt_id, @r###" JWT( JwtId { id: AgentId( @@ -465,5 +441,5 @@ mod tests { }, ) "###); - } + } } diff --git a/crates/common/src/import.rs b/crates/common/src/import.rs deleted file mode 100644 index e4f7790c1..000000000 --- a/crates/common/src/import.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::{ - fs::File, - io::{self, Read}, - path::PathBuf, -}; - -use thiserror::Error; -use url::Url; - -#[derive(Error, Debug)] -pub enum FromUrlError { - #[error("HTTP error while attempting to read from URL: {0}")] - HTTP(#[from] reqwest::Error), - - #[error("Invalid URL scheme: {0}")] - InvalidUrlScheme(String), - - #[error("IO error while attempting to read from URL: {0}")] - IO(#[from] std::io::Error), -} - -pub enum PathOrUrl { - File(PathBuf), - Url(Url), -} - -pub async fn load_bytes_from_url(url: &str) -> Result, FromUrlError> { - let path_or_url = match url.parse::() { - Ok(url) => PathOrUrl::Url(url), - Err(_) => PathOrUrl::File(PathBuf::from(url)), - }; - - let content = match path_or_url { - PathOrUrl::File(path) => { - let mut file = File::open(path)?; - let mut buf = Vec::new(); - file.read_to_end(&mut buf)?; - Ok(buf) - } - PathOrUrl::Url(url) => match url.scheme() { - "file" => { - let mut file = File::open(url.path())?; - let mut buf = Vec::new(); - file.read_to_end(&mut buf)?; - Ok(buf) - } - "http" | "https" => Ok(reqwest::get(url).await?.bytes().await?.into()), - _ => Err(FromUrlError::InvalidUrlScheme(url.scheme().to_owned())), - }, - }?; - - Ok(content) -} - -pub fn load_bytes_from_stdin() -> Result, io::Error> { - let mut buffer = Vec::new(); - let mut stdin = io::stdin(); - let _ = stdin.read_to_end(&mut buffer)?; - Ok(buffer) -} diff --git a/crates/common/src/ledger.rs b/crates/common/src/ledger.rs index 258c80f62..f4edf1fb6 100644 --- a/crates/common/src/ledger.rs +++ b/crates/common/src/ledger.rs @@ -1,702 +1,624 @@ -use derivative::Derivative; - -use opa_tp_protocol::async_stl_client::{error::SawtoothCommunicationError, ledger::BlockId}; -use parity_scale_codec::MaxEncodedLen; use scale_info::TypeInfo; -use tracing::{instrument, trace}; +use tracing::instrument; use crate::{ - identity::SignedIdentity, - prov::{ - operations::{ - ActivityExists, ActivityUses, ActsOnBehalfOf, AgentExists, ChronicleOperation, - CreateNamespace, EndActivity, EntityDerive, EntityExists, SetAttributes, StartActivity, - WasAssociatedWith, WasAttributedTo, WasGeneratedBy, WasInformedBy, - }, - ActivityId, AgentId, ChronicleIri, ChronicleTransactionId, Contradiction, EntityId, - NamespaceId, ParseIriError, ProcessorError, ProvModel, - }, + identity::SignedIdentity, + prov::{ + operations::{ + ActivityExists, ActivityUses, ActsOnBehalfOf, AgentExists, ChronicleOperation, + CreateNamespace, EndActivity, EntityDerive, EntityExists, SetAttributes, StartActivity, + WasAssociatedWith, WasAttributedTo, WasGeneratedBy, WasInformedBy, + }, + ActivityId, AgentId, ChronicleIri, ChronicleTransactionId, Contradiction, EntityId, + NamespaceId, ParseIriError, ProcessorError, ProvModel, + }, }; +#[cfg(not(feature = "std"))] +use core::str::FromStr; +#[cfg(not(feature = "std"))] +use parity_scale_codec::{ + alloc::boxed::Box, alloc::collections::btree_map::Entry, alloc::collections::BTreeMap, + alloc::collections::BTreeSet, alloc::string::String, alloc::sync::Arc, alloc::vec::Vec, +}; +#[cfg(not(feature = "std"))] +use scale_info::prelude::*; +#[cfg(feature = "std")] use std::{ - collections::{BTreeMap, HashSet}, - fmt::{Display, Formatter}, - str::FromStr, - sync::Arc, + boxed::Box, collections::btree_map::Entry, collections::BTreeMap, collections::BTreeSet, + str::FromStr, sync::Arc, }; -#[derive(Derivative, Clone)] -#[derivative(Debug)] +#[derive(Debug, Clone)] pub enum SubmissionError { - Communication { - source: Arc, - tx_id: ChronicleTransactionId, - }, - Processor { - source: Arc, - tx_id: ChronicleTransactionId, - }, - Contradiction { - source: Contradiction, - tx_id: ChronicleTransactionId, - }, + Communication { source: Arc, tx_id: ChronicleTransactionId }, + Processor { source: Arc, tx_id: ChronicleTransactionId }, + Contradiction { source: Contradiction, tx_id: ChronicleTransactionId }, } impl SubmissionError { - pub fn tx_id(&self) -> &ChronicleTransactionId { - match self { - SubmissionError::Communication { tx_id, .. } => tx_id, - SubmissionError::Processor { tx_id, .. } => tx_id, - SubmissionError::Contradiction { tx_id, .. } => tx_id, - } - } - - pub fn processor(tx_id: &ChronicleTransactionId, source: ProcessorError) -> SubmissionError { - SubmissionError::Processor { - source: Arc::new(source), - tx_id: tx_id.clone(), - } - } - - pub fn contradiction(tx_id: &ChronicleTransactionId, source: Contradiction) -> SubmissionError { - SubmissionError::Contradiction { - source, - tx_id: tx_id.clone(), - } - } - - pub fn communication( - tx_id: &ChronicleTransactionId, - source: SawtoothCommunicationError, - ) -> SubmissionError { - SubmissionError::Communication { - source: Arc::new(source), - tx_id: tx_id.clone(), - } - } + pub fn tx_id(&self) -> &ChronicleTransactionId { + match self { + SubmissionError::Communication { tx_id, .. } => tx_id, + SubmissionError::Processor { tx_id, .. } => tx_id, + SubmissionError::Contradiction { tx_id, .. } => tx_id, + } + } + + pub fn processor(tx_id: &ChronicleTransactionId, source: ProcessorError) -> SubmissionError { + SubmissionError::Processor { source: Arc::new(source), tx_id: tx_id.clone() } + } + + pub fn contradiction(tx_id: &ChronicleTransactionId, source: Contradiction) -> SubmissionError { + SubmissionError::Contradiction { source, tx_id: tx_id.clone() } + } + + pub fn communication(tx_id: &ChronicleTransactionId, source: anyhow::Error) -> SubmissionError { + SubmissionError::Communication { source: Arc::new(source), tx_id: tx_id.clone() } + } } #[derive(Debug)] pub enum SubscriptionError { - Implementation { - source: Box, - }, -} - -impl Display for SubscriptionError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Implementation { .. } => write!(f, "Subscription error"), - } - } -} - -impl std::error::Error for SubscriptionError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Implementation { source } => Some(source.as_ref()), - } - } + Implementation { source: anyhow::Error }, } -impl Display for SubmissionError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Communication { source, .. } => write!(f, "Ledger error {source} "), - Self::Processor { source, .. } => write!(f, "Processor error {source} "), - Self::Contradiction { source, .. } => write!(f, "Contradiction: {source}"), - } - } +impl core::fmt::Display for SubscriptionError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Implementation { .. } => write!(f, "Subscription error"), + } + } } -impl std::error::Error for SubmissionError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Communication { source, .. } => Some(source), - Self::Processor { source, .. } => Some(source), - Self::Contradiction { source, .. } => Some(source), - } - } +impl core::fmt::Display for SubmissionError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Communication { source, .. } => write!(f, "Ledger error {source} "), + Self::Processor { source, .. } => write!(f, "Processor error {source} "), + Self::Contradiction { source, .. } => write!(f, "Contradiction: {source}"), + } + } } pub type SubmitResult = Result; #[derive(Debug, Clone)] pub struct Commit { - pub tx_id: ChronicleTransactionId, - pub block_id: BlockId, - pub delta: Box, + pub tx_id: ChronicleTransactionId, + pub block_id: String, + pub delta: Box, } impl Commit { - pub fn new(tx_id: ChronicleTransactionId, block_id: BlockId, delta: Box) -> Self { - Commit { - tx_id, - block_id, - delta, - } - } + pub fn new(tx_id: ChronicleTransactionId, block_id: String, delta: Box) -> Self { + Commit { tx_id, block_id, delta } + } } pub type CommitResult = Result; #[derive(Debug, Clone)] pub enum SubmissionStage { - Submitted(SubmitResult), - Committed(Commit, Box), - NotCommitted((ChronicleTransactionId, Contradiction, Box)), + Submitted(SubmitResult), + Committed(Commit, Box), + NotCommitted((ChronicleTransactionId, Contradiction, Box)), } impl SubmissionStage { - pub fn submitted_error(r: &SubmissionError) -> Self { - SubmissionStage::Submitted(Err(r.clone())) - } - - pub fn submitted(r: &ChronicleTransactionId) -> Self { - SubmissionStage::Submitted(Ok(r.clone())) - } - - pub fn committed(commit: Commit, identity: SignedIdentity) -> Self { - SubmissionStage::Committed(commit, identity.into()) - } - - pub fn not_committed( - tx: ChronicleTransactionId, - contradiction: Contradiction, - identity: SignedIdentity, - ) -> Self { - SubmissionStage::NotCommitted((tx, contradiction, identity.into())) - } - - pub fn tx_id(&self) -> &ChronicleTransactionId { - match self { - Self::Submitted(tx_id) => match tx_id { - Ok(tx_id) => tx_id, - Err(e) => e.tx_id(), - }, - Self::Committed(commit, _) => &commit.tx_id, - Self::NotCommitted((tx_id, _, _)) => tx_id, - } - } + pub fn submitted_error(r: &SubmissionError) -> Self { + SubmissionStage::Submitted(Err(r.clone())) + } + + pub fn submitted(r: &ChronicleTransactionId) -> Self { + SubmissionStage::Submitted(Ok(r.clone())) + } + + pub fn committed(commit: Commit, identity: SignedIdentity) -> Self { + SubmissionStage::Committed(commit, identity.into()) + } + + pub fn not_committed( + tx: ChronicleTransactionId, + contradiction: Contradiction, + identity: SignedIdentity, + ) -> Self { + SubmissionStage::NotCommitted((tx, contradiction, identity.into())) + } + + pub fn tx_id(&self) -> &ChronicleTransactionId { + match self { + Self::Submitted(tx_id) => match tx_id { + Ok(tx_id) => tx_id, + Err(e) => e.tx_id(), + }, + Self::Committed(commit, _) => &commit.tx_id, + Self::NotCommitted((tx_id, _, _)) => tx_id, + } + } } #[derive( - parity_scale_codec::Encode, - parity_scale_codec::Decode, - TypeInfo, - PartialEq, - Eq, - PartialOrd, - Ord, - Debug, - Clone, + parity_scale_codec::Encode, + parity_scale_codec::Decode, + TypeInfo, + PartialEq, + Eq, + PartialOrd, + Ord, + Debug, + Clone, )] pub struct LedgerAddress { - // Namespaces do not have a namespace - namespace: Option, - resource: ChronicleIri, + // Namespaces do not have a namespace + namespace: Option, + resource: ChronicleIri, } -impl MaxEncodedLen for LedgerAddress { - fn max_encoded_len() -> usize { - 2048usize - } +impl parity_scale_codec::MaxEncodedLen for LedgerAddress { + fn max_encoded_len() -> usize { + 2048usize + } } -impl Display for LedgerAddress { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if let Some(namespace) = &self.namespace { - write!(f, "{}:{}", namespace, self.resource) - } else { - write!(f, "{}", self.resource) - } - } +impl core::fmt::Display for LedgerAddress { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + if let Some(namespace) = &self.namespace { + write!(f, "{}:{}", namespace, self.resource) + } else { + write!(f, "{}", self.resource) + } + } } pub trait NameSpacePart { - fn namespace_part(&self) -> Option; + fn namespace_part(&self) -> Option; } impl NameSpacePart for LedgerAddress { - fn namespace_part(&self) -> Option { - self.namespace.clone() - } + fn namespace_part(&self) -> Option { + self.namespace.clone() + } } pub trait ResourcePart { - fn resource_part(&self) -> ChronicleIri; + fn resource_part(&self) -> ChronicleIri; } impl ResourcePart for LedgerAddress { - fn resource_part(&self) -> ChronicleIri { - self.resource.clone() - } + fn resource_part(&self) -> ChronicleIri { + self.resource.clone() + } } impl LedgerAddress { - fn from_ld(ns: Option<&str>, resource: &str) -> Result { - Ok(Self { - namespace: if let Some(ns) = ns { - Some(ChronicleIri::from_str(ns)?.namespace()?) - } else { - None - }, - resource: ChronicleIri::from_str(resource)?, - }) - } - - fn namespace(ns: &NamespaceId) -> Self { - Self { - namespace: None, - resource: ns.clone().into(), - } - } - - fn in_namespace(ns: &NamespaceId, resource: impl Into) -> Self { - Self { - namespace: Some(ns.clone()), - resource: resource.into(), - } - } + fn from_ld(ns: Option<&str>, resource: &str) -> Result { + Ok(Self { + namespace: if let Some(ns) = ns { + Some(ChronicleIri::from_str(ns)?.namespace()?) + } else { + None + }, + resource: ChronicleIri::from_str(resource)?, + }) + } + + fn namespace(ns: &NamespaceId) -> Self { + Self { namespace: None, resource: ns.clone().into() } + } + + fn in_namespace(ns: &NamespaceId, resource: impl Into) -> Self { + Self { namespace: Some(ns.clone()), resource: resource.into() } + } } -// Split a ProvModel into a snapshot list of its components - Namespaces, Entities, Activities and Agents +// Split a ProvModel into a snapshot list of its components - Namespaces, Entities, Activities and +// Agents pub trait ProvSnapshot { - fn to_snapshot(&self) -> Vec<((Option, ChronicleIri), ProvModel)>; + fn to_snapshot(&self) -> Vec<((Option, ChronicleIri), ProvModel)>; } impl ProvSnapshot for ProvModel { - fn to_snapshot(&self) -> Vec<((Option, ChronicleIri), ProvModel)> { - let mut snapshot = Vec::new(); - - for (namespace_id, namespace) in &self.namespaces { - snapshot.push(( - (None, namespace_id.clone().into()), - ProvModel { - namespaces: vec![(namespace_id.clone(), namespace.clone())] - .into_iter() - .collect(), - ..Default::default() - }, - )); - } - - for ((ns, agent_id), agent) in &self.agents { - let mut delegation = BTreeMap::new(); - if let Some(delegation_set) = self.delegation.get(&(ns.clone(), agent_id.clone())) { - delegation.insert((ns.clone(), agent_id.clone()), delegation_set.clone()); - } - let mut acted_on_behalf_of = BTreeMap::new(); - if let Some(acted_on_behalf_of_set) = - self.acted_on_behalf_of.get(&(ns.clone(), agent_id.clone())) - { - acted_on_behalf_of.insert( - (ns.clone(), agent_id.clone()), - acted_on_behalf_of_set.clone(), - ); - } - snapshot.push(( - (Some(ns.clone()), agent_id.clone().into()), - ProvModel { - agents: vec![((ns.clone(), agent_id.clone()), agent.clone())] - .into_iter() - .collect(), - delegation, - acted_on_behalf_of, - ..Default::default() - }, - )); - } - - for ((ns, activity_id), activity) in &self.activities { - let mut was_informed_by = BTreeMap::new(); - if let Some(was_informed_by_set) = - self.was_informed_by.get(&(ns.clone(), activity_id.clone())) - { - was_informed_by.insert( - (ns.clone(), activity_id.clone()), - was_informed_by_set.clone(), - ); - } - let mut generated = BTreeMap::new(); - if let Some(generated_set) = self.generated.get(&(ns.clone(), activity_id.clone())) { - generated.insert((ns.clone(), activity_id.clone()), generated_set.clone()); - } - let mut usage = BTreeMap::new(); - if let Some(usage_set) = self.usage.get(&(ns.clone(), activity_id.clone())) { - usage.insert((ns.clone(), activity_id.clone()), usage_set.clone()); - } - let mut association = BTreeMap::new(); - if let Some(association_set) = self.association.get(&(ns.clone(), activity_id.clone())) - { - association.insert((ns.clone(), activity_id.clone()), association_set.clone()); - } - - snapshot.push(( - (Some(ns.clone()), activity_id.clone().into()), - ProvModel { - activities: vec![((ns.clone(), activity_id.clone()), activity.clone())] - .into_iter() - .collect(), - was_informed_by, - usage, - generated, - association, - ..Default::default() - }, - )); - } - - for ((ns, entity_id), entity) in &self.entities { - let mut derivation = BTreeMap::new(); - if let Some(derivation_set) = self.derivation.get(&(ns.clone(), entity_id.clone())) { - derivation.insert((ns.clone(), entity_id.clone()), derivation_set.clone()); - } - let mut generation = BTreeMap::new(); - if let Some(generation_set) = self.generation.get(&(ns.clone(), entity_id.clone())) { - generation.insert((ns.clone(), entity_id.clone()), generation_set.clone()); - } - let mut attribution = BTreeMap::new(); - if let Some(attribution_set) = self.attribution.get(&(ns.clone(), entity_id.clone())) { - attribution.insert((ns.clone(), entity_id.clone()), attribution_set.clone()); - } - snapshot.push(( - (Some(ns.clone()), entity_id.clone().into()), - ProvModel { - entities: vec![((ns.clone(), entity_id.clone()), entity.clone())] - .into_iter() - .collect(), - derivation, - generation, - attribution, - ..Default::default() - }, - )); - } - - snapshot - } + fn to_snapshot(&self) -> Vec<((Option, ChronicleIri), ProvModel)> { + let mut snapshot = Vec::new(); + + for (namespace_id, namespace) in &self.namespaces { + snapshot.push(( + (None, namespace_id.clone().into()), + ProvModel { + namespaces: vec![(namespace_id.clone(), namespace.clone())] + .into_iter() + .collect(), + ..Default::default() + }, + )); + } + + for ((ns, agent_id), agent) in &self.agents { + let mut delegation = BTreeMap::new(); + if let Some(delegation_set) = self.delegation.get(&(ns.clone(), agent_id.clone())) { + delegation.insert((ns.clone(), agent_id.clone()), delegation_set.clone()); + } + let mut acted_on_behalf_of = BTreeMap::new(); + if let Some(acted_on_behalf_of_set) = + self.acted_on_behalf_of.get(&(ns.clone(), agent_id.clone())) + { + acted_on_behalf_of + .insert((ns.clone(), agent_id.clone()), acted_on_behalf_of_set.clone()); + } + snapshot.push(( + (Some(ns.clone()), agent_id.clone().into()), + ProvModel { + agents: vec![((ns.clone(), agent_id.clone()), agent.clone())] + .into_iter() + .collect(), + delegation, + acted_on_behalf_of, + ..Default::default() + }, + )); + } + + for ((ns, activity_id), activity) in &self.activities { + let mut was_informed_by = BTreeMap::new(); + if let Some(was_informed_by_set) = + self.was_informed_by.get(&(ns.clone(), activity_id.clone())) + { + was_informed_by + .insert((ns.clone(), activity_id.clone()), was_informed_by_set.clone()); + } + let mut generated = BTreeMap::new(); + if let Some(generated_set) = self.generated.get(&(ns.clone(), activity_id.clone())) { + generated.insert((ns.clone(), activity_id.clone()), generated_set.clone()); + } + let mut usage = BTreeMap::new(); + if let Some(usage_set) = self.usage.get(&(ns.clone(), activity_id.clone())) { + usage.insert((ns.clone(), activity_id.clone()), usage_set.clone()); + } + let mut association = BTreeMap::new(); + if let Some(association_set) = self.association.get(&(ns.clone(), activity_id.clone())) + { + association.insert((ns.clone(), activity_id.clone()), association_set.clone()); + } + + snapshot.push(( + (Some(ns.clone()), activity_id.clone().into()), + ProvModel { + activities: vec![((ns.clone(), activity_id.clone()), activity.clone())] + .into_iter() + .collect(), + was_informed_by, + usage, + generated, + association, + ..Default::default() + }, + )); + } + + for ((ns, entity_id), entity) in &self.entities { + let mut derivation = BTreeMap::new(); + if let Some(derivation_set) = self.derivation.get(&(ns.clone(), entity_id.clone())) { + derivation.insert((ns.clone(), entity_id.clone()), derivation_set.clone()); + } + let mut generation = BTreeMap::new(); + if let Some(generation_set) = self.generation.get(&(ns.clone(), entity_id.clone())) { + generation.insert((ns.clone(), entity_id.clone()), generation_set.clone()); + } + let mut attribution = BTreeMap::new(); + if let Some(attribution_set) = self.attribution.get(&(ns.clone(), entity_id.clone())) { + attribution.insert((ns.clone(), entity_id.clone()), attribution_set.clone()); + } + snapshot.push(( + (Some(ns.clone()), entity_id.clone().into()), + ProvModel { + entities: vec![((ns.clone(), entity_id.clone()), entity.clone())] + .into_iter() + .collect(), + derivation, + generation, + attribution, + ..Default::default() + }, + )); + } + + snapshot + } } #[derive(Debug, Clone)] pub struct StateInput { - data: ProvModel, + data: ProvModel, } impl StateInput { - pub fn new(data: ProvModel) -> Self { - Self { data } - } + pub fn new(data: ProvModel) -> Self { + Self { data } + } - pub fn data(&self) -> &ProvModel { - &self.data - } + pub fn data(&self) -> &ProvModel { + &self.data + } } #[derive(Debug)] pub struct StateOutput { - pub address: LedgerAddress, - pub data: ProvModel, + pub address: LedgerAddress, + pub data: ProvModel, } impl StateOutput { - pub fn new(address: LedgerAddress, data: ProvModel) -> Self { - Self { address, data } - } + pub fn new(address: LedgerAddress, data: ProvModel) -> Self { + Self { address, data } + } - pub fn address(&self) -> &LedgerAddress { - &self.address - } + pub fn address(&self) -> &LedgerAddress { + &self.address + } - pub fn data(&self) -> &ProvModel { - &self.data - } + pub fn data(&self) -> &ProvModel { + &self.data + } } #[derive(Debug, Clone)] pub struct Version { - pub(crate) version: u32, - pub(crate) value: Option, + pub(crate) version: u32, + pub(crate) value: Option, } impl Version { - pub fn write(&mut self, value: Option) { - if value != self.value { - self.version += 1; - self.value = value - } - } + pub fn write(&mut self, value: Option) { + if value != self.value { + self.version += 1; + self.value = value + } + } } /// Hold a cache of `LedgerWriter::submit` input and output address data pub struct OperationState { - state: BTreeMap, + state: BTreeMap, } impl Default for OperationState { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } impl OperationState { - pub fn new() -> Self { - Self { - state: BTreeMap::new(), - } - } - - pub fn update_state_from_output(&mut self, output: impl Iterator) { - self.update_state(output.map(|output| (output.address, Some(output.data)))) - } - /// Load input values into `OperationState` - pub fn update_state( - &mut self, - input: impl Iterator)>, - ) { - input.for_each(|(address, value)| { - let entry = self.state.entry(address); - if let std::collections::btree_map::Entry::Vacant(e) = entry { - e.insert(Version { version: 0, value }); - } else if let std::collections::btree_map::Entry::Occupied(mut e) = entry { - e.get_mut().write(value); - } - }); - } - - /// Return the input data held in `OperationState` - /// as a vector of `StateInput`s - pub fn input(&self) -> Vec { - self.state - .values() - .cloned() - .filter_map(|v| v.value.map(StateInput::new)) - .collect() - } - - /// Check if the data associated with an address has changed in processing - /// while outputting a stream of dirty `StateOutput`s - pub fn dirty(self) -> impl Iterator { - self.state - .into_iter() - .filter_map(|(addr, data)| { - if data.version > 0 { - data.value.map(|value| (StateOutput::new(addr, value))) - } else { - None - } - }) - .collect::>() - .into_iter() - } - - /// Return the input data held in `OperationState` for `addresses` as a vector of `StateInput`s - pub fn opa_context(&self, addresses: HashSet) -> Vec { - self.state - .iter() - .filter(|(addr, _data)| addresses.iter().any(|a| &a == addr)) - .map(|(_, data)| data.clone()) - .filter_map(|v| v.value.map(StateInput::new)) - .collect() - } + pub fn new() -> Self { + Self { state: BTreeMap::new() } + } + + pub fn update_state_from_output(&mut self, output: impl Iterator) { + self.update_state(output.map(|output| (output.address, Some(output.data)))) + } + /// Load input values into `OperationState` + pub fn update_state( + &mut self, + input: impl Iterator)>, + ) { + input.for_each(|(address, value)| { + let entry = self.state.entry(address); + if let Entry::Vacant(e) = entry { + e.insert(Version { version: 0, value }); + } else if let Entry::Occupied(mut e) = entry { + e.get_mut().write(value); + } + }); + } + + /// Return the input data held in `OperationState` + /// as a vector of `StateInput`s + pub fn input(&self) -> Vec { + self.state + .values() + .cloned() + .filter_map(|v| v.value.map(StateInput::new)) + .collect() + } + + /// Check if the data associated with an address has changed in processing + /// while outputting a stream of dirty `StateOutput`s + pub fn dirty(self) -> impl Iterator { + self.state + .into_iter() + .filter_map(|(addr, data)| { + if data.version > 0 { + data.value.map(|value| (StateOutput::new(addr, value))) + } else { + None + } + }) + .collect::>() + .into_iter() + } + + /// Return the input data held in `OperationState` for `addresses` as a vector of `StateInput`s + pub fn opa_context(&self, addresses: BTreeSet) -> Vec { + self.state + .iter() + .filter(|(addr, _data)| addresses.iter().any(|a| &a == addr)) + .map(|(_, data)| data.clone()) + .filter_map(|v| v.value.map(StateInput::new)) + .collect() + } } impl ChronicleOperation { - /// Compute dependencies for a chronicle operation, input and output addresses are always symmetric - pub fn dependencies(&self) -> Vec { - match self { - ChronicleOperation::CreateNamespace(CreateNamespace { id, .. }) => { - vec![LedgerAddress::namespace(id)] - } - ChronicleOperation::AgentExists(AgentExists { - namespace, - external_id, - .. - }) => { - vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, AgentId::from_external_id(external_id)), - ] - } - ChronicleOperation::ActivityExists(ActivityExists { - namespace, - external_id, - .. - }) => { - vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace( - namespace, - ActivityId::from_external_id(external_id), - ), - ] - } - ChronicleOperation::StartActivity(StartActivity { namespace, id, .. }) => { - vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, id.clone()), - ] - } - ChronicleOperation::WasAssociatedWith(WasAssociatedWith { - id, - namespace, - activity_id, - agent_id, - .. - }) => vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, id.clone()), - LedgerAddress::in_namespace(namespace, activity_id.clone()), - LedgerAddress::in_namespace(namespace, agent_id.clone()), - ], - ChronicleOperation::WasAttributedTo(WasAttributedTo { - id, - namespace, - entity_id, - agent_id, - .. - }) => vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, id.clone()), - LedgerAddress::in_namespace(namespace, entity_id.clone()), - LedgerAddress::in_namespace(namespace, agent_id.clone()), - ], - ChronicleOperation::EndActivity(EndActivity { namespace, id, .. }) => { - vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, id.clone()), - ] - } - ChronicleOperation::ActivityUses(ActivityUses { - namespace, - id, - activity, - }) => { - vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, activity.clone()), - LedgerAddress::in_namespace(namespace, id.clone()), - ] - } - ChronicleOperation::EntityExists(EntityExists { - namespace, - external_id, - }) => { - vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, EntityId::from_external_id(external_id)), - ] - } - ChronicleOperation::WasGeneratedBy(WasGeneratedBy { - namespace, - id, - activity, - }) => vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, activity.clone()), - LedgerAddress::in_namespace(namespace, id.clone()), - ], - ChronicleOperation::WasInformedBy(WasInformedBy { - namespace, - activity, - informing_activity, - }) => { - vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, activity.clone()), - LedgerAddress::in_namespace(namespace, informing_activity.clone()), - ] - } - ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf { - namespace, - id, - delegate_id, - activity_id, - responsible_id, - .. - }) => vec![ - Some(LedgerAddress::namespace(namespace)), - activity_id - .as_ref() - .map(|activity_id| LedgerAddress::in_namespace(namespace, activity_id.clone())), - Some(LedgerAddress::in_namespace(namespace, delegate_id.clone())), - Some(LedgerAddress::in_namespace( - namespace, - responsible_id.clone(), - )), - Some(LedgerAddress::in_namespace(namespace, id.clone())), - ] - .into_iter() - .flatten() - .collect(), - ChronicleOperation::EntityDerive(EntityDerive { - namespace, - id, - used_id, - activity_id, - .. - }) => vec![ - Some(LedgerAddress::namespace(namespace)), - activity_id - .as_ref() - .map(|activity_id| LedgerAddress::in_namespace(namespace, activity_id.clone())), - Some(LedgerAddress::in_namespace(namespace, used_id.clone())), - Some(LedgerAddress::in_namespace(namespace, id.clone())), - ] - .into_iter() - .flatten() - .collect(), - ChronicleOperation::SetAttributes(SetAttributes::Agent { id, namespace, .. }) => { - vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, id.clone()), - ] - } - ChronicleOperation::SetAttributes(SetAttributes::Entity { id, namespace, .. }) => { - vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, id.clone()), - ] - } - ChronicleOperation::SetAttributes(SetAttributes::Activity { - id, namespace, .. - }) => { - vec![ - LedgerAddress::namespace(namespace), - LedgerAddress::in_namespace(namespace, id.clone()), - ] - } - } - } - - /// Apply an operation's input states to the prov model - /// Take input states and apply them to the prov model, then apply transaction, - /// then return a snapshot of output state for diff calculation - #[instrument(level = "debug", skip(self, model, input))] - pub fn process( - &self, - mut model: ProvModel, - input: Vec, - ) -> Result<(Vec, ProvModel), ProcessorError> { - for input in input.iter() { - model.combine(input.data()) - } - model.apply(self)?; - Ok(( - model - .to_snapshot() - .into_iter() - .map(|((namespace, resource), prov)| { - StateOutput::new( - LedgerAddress { - namespace, - resource, - }, - prov, - ) - }) - .collect::>(), - model, - )) - } + /// Compute dependencies for a chronicle operation, input and output addresses are always + /// symmetric + pub fn dependencies(&self) -> Vec { + match self { + ChronicleOperation::CreateNamespace(CreateNamespace { id, .. }) => { + vec![LedgerAddress::namespace(id)] + }, + ChronicleOperation::AgentExists(AgentExists { namespace, external_id, .. }) => { + vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, AgentId::from_external_id(external_id)), + ] + }, + ChronicleOperation::ActivityExists(ActivityExists { + namespace, external_id, .. + }) => { + vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace( + namespace, + ActivityId::from_external_id(external_id), + ), + ] + }, + ChronicleOperation::StartActivity(StartActivity { namespace, id, .. }) => { + vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, id.clone()), + ] + }, + ChronicleOperation::WasAssociatedWith(WasAssociatedWith { + id, + namespace, + activity_id, + agent_id, + .. + }) => vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, id.clone()), + LedgerAddress::in_namespace(namespace, activity_id.clone()), + LedgerAddress::in_namespace(namespace, agent_id.clone()), + ], + ChronicleOperation::WasAttributedTo(WasAttributedTo { + id, + namespace, + entity_id, + agent_id, + .. + }) => vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, id.clone()), + LedgerAddress::in_namespace(namespace, entity_id.clone()), + LedgerAddress::in_namespace(namespace, agent_id.clone()), + ], + ChronicleOperation::EndActivity(EndActivity { namespace, id, .. }) => { + vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, id.clone()), + ] + }, + ChronicleOperation::ActivityUses(ActivityUses { namespace, id, activity }) => { + vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, activity.clone()), + LedgerAddress::in_namespace(namespace, id.clone()), + ] + }, + ChronicleOperation::EntityExists(EntityExists { namespace, external_id }) => { + vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, EntityId::from_external_id(external_id)), + ] + }, + ChronicleOperation::WasGeneratedBy(WasGeneratedBy { namespace, id, activity }) => vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, activity.clone()), + LedgerAddress::in_namespace(namespace, id.clone()), + ], + ChronicleOperation::WasInformedBy(WasInformedBy { + namespace, + activity, + informing_activity, + }) => { + vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, activity.clone()), + LedgerAddress::in_namespace(namespace, informing_activity.clone()), + ] + }, + ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf { + namespace, + id, + delegate_id, + activity_id, + responsible_id, + .. + }) => vec![ + Some(LedgerAddress::namespace(namespace)), + activity_id + .as_ref() + .map(|activity_id| LedgerAddress::in_namespace(namespace, activity_id.clone())), + Some(LedgerAddress::in_namespace(namespace, delegate_id.clone())), + Some(LedgerAddress::in_namespace(namespace, responsible_id.clone())), + Some(LedgerAddress::in_namespace(namespace, id.clone())), + ] + .into_iter() + .flatten() + .collect(), + ChronicleOperation::EntityDerive(EntityDerive { + namespace, + id, + used_id, + activity_id, + .. + }) => vec![ + Some(LedgerAddress::namespace(namespace)), + activity_id + .as_ref() + .map(|activity_id| LedgerAddress::in_namespace(namespace, activity_id.clone())), + Some(LedgerAddress::in_namespace(namespace, used_id.clone())), + Some(LedgerAddress::in_namespace(namespace, id.clone())), + ] + .into_iter() + .flatten() + .collect(), + ChronicleOperation::SetAttributes(SetAttributes::Agent { id, namespace, .. }) => { + vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, id.clone()), + ] + }, + ChronicleOperation::SetAttributes(SetAttributes::Entity { id, namespace, .. }) => { + vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, id.clone()), + ] + }, + ChronicleOperation::SetAttributes(SetAttributes::Activity { + id, namespace, .. + }) => { + vec![ + LedgerAddress::namespace(namespace), + LedgerAddress::in_namespace(namespace, id.clone()), + ] + }, + } + } + + /// Apply an operation's input states to the prov model + /// Take input states and apply them to the prov model, then apply transaction, + /// then return a snapshot of output state for diff calculation + #[instrument(level = "debug", skip(self, model, input))] + pub fn process( + &self, + mut model: ProvModel, + input: Vec, + ) -> Result<(Vec, ProvModel), ProcessorError> { + for input in input.iter() { + model.combine(input.data()) + } + model.apply(self).map_err(ProcessorError::Contradiction)?; + Ok(( + model + .to_snapshot() + .into_iter() + .map(|((namespace, resource), prov)| { + StateOutput::new(LedgerAddress { namespace, resource }, prov) + }) + .collect::>(), + model, + )) + } } diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index a39a3eb52..1065a21fe 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,17 +1,12 @@ +#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "strict", deny(warnings))] #[macro_use] extern crate serde_derive; -#[macro_use] -extern crate iref_enum; pub mod attributes; -pub mod commands; pub mod context; -pub mod database; pub mod identity; -pub mod import; pub mod ledger; -pub mod opa; pub mod prov; pub use k256; diff --git a/crates/common/src/opa.rs b/crates/common/src/opa.rs deleted file mode 100644 index 9657edbfa..000000000 --- a/crates/common/src/opa.rs +++ /dev/null @@ -1,674 +0,0 @@ -use crate::{ - identity::{AuthId, IdentityError, OpaData}, - import::{load_bytes_from_url, FromUrlError}, -}; -use k256::sha2::{Digest, Sha256}; -use opa::{bundle::Bundle, wasm::Opa}; -use opa_tp_protocol::{ - address::{FAMILY, VERSION}, - async_stl_client::{ - error::SawtoothCommunicationError, - ledger::LedgerReader, - zmq_client::{HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel}, - }, - state::policy_address, - OpaLedger, -}; -use rust_embed::RustEmbed; -use std::{net::SocketAddr, sync::Arc}; -use thiserror::Error; -use tokio::sync::Mutex; -use tracing::{debug, error, info, instrument}; - -#[derive(Debug, Error)] -pub enum PolicyLoaderError { - #[error("Failed to read embedded OPA policies")] - EmbeddedOpaPolicies, - - #[error("Policy not found: {0}")] - MissingPolicy(String), - - #[error("OPA bundle I/O error: {0}")] - OpaBundleError(#[from] opa::bundle::Error), - - #[error("Error loading OPA policy: {0}")] - SawtoothCommunicationError(#[from] SawtoothCommunicationError), - - #[error("Error loading policy bundle from URL: {0}")] - UrlError(#[from] FromUrlError), -} - -#[async_trait::async_trait] -pub trait PolicyLoader { - /// Set address of OPA policy - fn set_address(&mut self, address: &str); - - /// Set OPA policy - fn set_rule_name(&mut self, policy: &str); - - /// Set entrypoint for OPA policy - fn set_entrypoint(&mut self, entrypoint: &str); - - fn get_address(&self) -> &str; - - fn get_rule_name(&self) -> &str; - - fn get_entrypoint(&self) -> &str; - - fn get_policy(&self) -> &[u8]; - - /// Load OPA policy from address set in `PolicyLoader` - async fn load_policy(&mut self) -> Result<(), PolicyLoaderError>; - - /// Load OPA policy from provided bytes - fn load_policy_from_bytes(&mut self, policy: &[u8]); - - /// Return a built OPA instance from the cached policy - #[instrument(level = "trace", skip(self), ret)] - fn build_opa(&self) -> Result { - Ok(Opa::new().build(self.get_policy())?) - } - - /// Load OPA policy from provided policy bundle - fn load_policy_from_bundle(&mut self, bundle: &Bundle) -> Result<(), PolicyLoaderError> { - let rule = self.get_rule_name(); - self.load_policy_from_bytes( - bundle - .wasm_policies - .iter() - .find(|p| p.entrypoint == rule) - .map(|p| p.bytes.as_ref()) - .ok_or(PolicyLoaderError::MissingPolicy(rule.to_string()))?, - ); - Ok(()) - } - - fn hash(&self) -> String; -} - -pub struct SawtoothPolicyLoader { - policy_id: String, - address: String, - policy: Option>, - entrypoint: String, - ledger: OpaLedger, -} - -impl SawtoothPolicyLoader { - pub fn new( - address: &SocketAddr, - policy_id: &str, - entrypoint: &str, - ) -> Result { - Ok(Self { - policy_id: policy_id.to_owned(), - address: String::default(), - policy: None, - entrypoint: entrypoint.to_owned(), - ledger: OpaLedger::new( - ZmqRequestResponseSawtoothChannel::new( - "sawtooth_policy", - &[address.to_owned()], - HighestBlockValidatorSelector, - )? - .retrying(), - FAMILY, - VERSION, - ), - }) - } - - fn sawtooth_address(&self, policy: impl AsRef) -> String { - policy_address(policy) - } - - #[instrument(level = "debug", skip(self))] - async fn load_bundle_from_chain(&mut self) -> Result, SawtoothCommunicationError> { - if let Some(policy) = self.policy.as_ref() { - return Ok(policy.clone()); - } - let load_policy_from = self.sawtooth_address(&self.policy_id); - debug!(load_policy_from=?load_policy_from); - - loop { - let res = self.ledger.get_state_entry(&load_policy_from).await; - - if let Err(res) = &res { - error!(error=?res, "Failed to load policy from chain"); - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - continue; - } - - return Ok(res.unwrap()); - } - } -} - -#[async_trait::async_trait] -impl PolicyLoader for SawtoothPolicyLoader { - fn set_address(&mut self, address: &str) { - self.address = address.to_owned() - } - - fn set_rule_name(&mut self, name: &str) { - self.policy_id = name.to_owned() - } - - fn set_entrypoint(&mut self, entrypoint: &str) { - self.entrypoint = entrypoint.to_owned() - } - - fn get_address(&self) -> &str { - &self.address - } - - fn get_rule_name(&self) -> &str { - &self.policy_id - } - - fn get_entrypoint(&self) -> &str { - &self.entrypoint - } - - fn get_policy(&self) -> &[u8] { - self.policy.as_ref().unwrap() - } - - async fn load_policy(&mut self) -> Result<(), PolicyLoaderError> { - let bundle = self.load_bundle_from_chain().await?; - info!(fetched_policy_bytes=?bundle.len(), "Fetched policy"); - if bundle.is_empty() { - error!("Policy not found: {}", self.get_rule_name()); - return Err(PolicyLoaderError::MissingPolicy( - self.get_rule_name().to_string(), - )); - } - self.load_policy_from_bundle(&Bundle::from_bytes(&*bundle)?) - } - - fn load_policy_from_bytes(&mut self, policy: &[u8]) { - self.policy = Some(policy.to_vec()) - } - - fn hash(&self) -> String { - hex::encode(Sha256::digest(self.policy.as_ref().unwrap())) - } -} - -/// OPA policy loader for policies passed via CLI or embedded in Chronicle -#[derive(Clone, Default)] -pub struct CliPolicyLoader { - address: String, - rule_name: String, - entrypoint: String, - policy: Vec, -} - -impl CliPolicyLoader { - pub fn new() -> Self { - Self { - ..Default::default() - } - } - - #[instrument(level = "trace", skip(self), ret)] - async fn get_policy_from_file(&mut self) -> Result, PolicyLoaderError> { - let bundle = Bundle::from_file(self.get_address())?; - - self.load_policy_from_bundle(&bundle)?; - - Ok(self.get_policy().to_vec()) - } - - /// Create a loaded [`CliPolicyLoader`] from name of an embedded dev policy and entrypoint - pub fn from_embedded_policy(policy: &str, entrypoint: &str) -> Result { - if let Some(file) = EmbeddedOpaPolicies::get("bundle.tar.gz") { - let bytes = file.data.as_ref(); - let bundle = Bundle::from_bytes(bytes)?; - let mut loader = CliPolicyLoader::new(); - loader.set_rule_name(policy); - loader.set_entrypoint(entrypoint); - loader.load_policy_from_bundle(&bundle)?; - Ok(loader) - } else { - Err(PolicyLoaderError::EmbeddedOpaPolicies) - } - } - - /// Create a loaded [`CliPolicyLoader`] from an OPA policy's bytes and entrypoint - pub fn from_policy_bytes( - policy: &str, - entrypoint: &str, - bytes: &[u8], - ) -> Result { - let mut loader = CliPolicyLoader::new(); - loader.set_rule_name(policy); - loader.set_entrypoint(entrypoint); - let bundle = Bundle::from_bytes(bytes)?; - loader.load_policy_from_bundle(&bundle)?; - Ok(loader) - } -} - -#[async_trait::async_trait] -impl PolicyLoader for CliPolicyLoader { - fn set_address(&mut self, address: &str) { - self.address = address.to_owned() - } - - fn set_rule_name(&mut self, name: &str) { - self.rule_name = name.to_owned() - } - - fn set_entrypoint(&mut self, entrypoint: &str) { - self.entrypoint = entrypoint.to_owned() - } - - fn get_address(&self) -> &str { - &self.address - } - - fn get_rule_name(&self) -> &str { - &self.rule_name - } - - fn get_entrypoint(&self) -> &str { - &self.entrypoint - } - - fn get_policy(&self) -> &[u8] { - &self.policy - } - - fn load_policy_from_bytes(&mut self, policy: &[u8]) { - self.policy = policy.to_vec() - } - - async fn load_policy(&mut self) -> Result<(), PolicyLoaderError> { - self.policy = self.get_policy_from_file().await?; - Ok(()) - } - - fn hash(&self) -> String { - hex::encode(Sha256::digest(&self.policy)) - } -} - -#[derive(Clone, Default)] -pub struct UrlPolicyLoader { - policy_id: String, - address: String, - policy: Vec, - entrypoint: String, -} - -impl UrlPolicyLoader { - pub fn new(url: &str, policy_id: &str, entrypoint: &str) -> Self { - Self { - address: url.into(), - policy_id: policy_id.to_owned(), - entrypoint: entrypoint.to_owned(), - ..Default::default() - } - } -} - -#[async_trait::async_trait] -impl PolicyLoader for UrlPolicyLoader { - fn set_address(&mut self, address: &str) { - self.address = address.to_owned(); - } - - fn set_rule_name(&mut self, name: &str) { - self.policy_id = name.to_owned(); - } - - fn set_entrypoint(&mut self, entrypoint: &str) { - self.entrypoint = entrypoint.to_owned(); - } - - fn get_address(&self) -> &str { - &self.address - } - - fn get_rule_name(&self) -> &str { - &self.policy_id - } - - fn get_entrypoint(&self) -> &str { - &self.entrypoint - } - - fn get_policy(&self) -> &[u8] { - &self.policy - } - - fn load_policy_from_bytes(&mut self, policy: &[u8]) { - self.policy = policy.to_vec(); - } - - async fn load_policy(&mut self) -> Result<(), PolicyLoaderError> { - let address = &self.address; - let bundle = load_bytes_from_url(address).await?; - - info!(loaded_policy_bytes=?bundle.len(), "Loaded policy bundle"); - - if bundle.is_empty() { - error!("Policy not found: {}", self.get_rule_name()); - return Err(PolicyLoaderError::MissingPolicy( - self.get_rule_name().to_string(), - )); - } - - self.load_policy_from_bundle(&Bundle::from_bytes(&*bundle)?) - } - - fn hash(&self) -> String { - hex::encode(Sha256::digest(&self.policy)) - } -} - -#[derive(Debug, Error)] -pub enum OpaExecutorError { - #[error("Access denied")] - AccessDenied, - - #[error("Identity error: {0}")] - IdentityError(#[from] IdentityError), - - #[error("Error loading OPA policy: {0}")] - PolicyLoaderError(#[from] PolicyLoaderError), - - #[error("Error evaluating OPA policy: {0}")] - OpaEvaluationError(#[from] anyhow::Error), -} - -#[async_trait::async_trait] -pub trait OpaExecutor { - /// Evaluate the loaded OPA instance against the provided identity and context - async fn evaluate(&mut self, id: &AuthId, context: &OpaData) -> Result<(), OpaExecutorError>; -} - -#[derive(Clone, Debug)] -pub struct ExecutorContext { - executor: Arc>, - hash: String, -} - -impl ExecutorContext { - #[instrument(skip(self), level = "trace", ret(Debug))] - pub async fn evaluate(&self, id: &AuthId, context: &OpaData) -> Result<(), OpaExecutorError> { - self.executor.lock().await.evaluate(id, context).await - } - - pub fn from_loader(loader: &L) -> Result { - Ok(Self { - executor: Arc::new(Mutex::new(WasmtimeOpaExecutor::from_loader(loader)?)), - hash: loader.hash(), - }) - } - - pub fn hash(&self) -> &str { - &self.hash - } -} - -#[derive(Debug)] -pub struct WasmtimeOpaExecutor { - opa: Opa, - entrypoint: String, -} - -impl WasmtimeOpaExecutor { - /// Build a `WasmtimeOpaExecutor` from the `PolicyLoader` provided - pub fn from_loader(loader: &L) -> Result { - Ok(Self { - opa: loader.build_opa()?, - entrypoint: loader.get_entrypoint().to_owned(), - }) - } -} - -#[async_trait::async_trait] -impl OpaExecutor for WasmtimeOpaExecutor { - #[instrument(level = "trace", skip(self))] - async fn evaluate(&mut self, id: &AuthId, context: &OpaData) -> Result<(), OpaExecutorError> { - self.opa.set_data(context)?; - let input = id.identity()?; - match self.opa.eval(&self.entrypoint, &input)? { - true => Ok(()), - false => Err(OpaExecutorError::AccessDenied), - } - } -} - -#[derive(RustEmbed)] -#[folder = "../../policies"] -#[include = "bundle.tar.gz"] -struct EmbeddedOpaPolicies; - -#[cfg(test)] -mod tests { - use super::*; - use crate::identity::IdentityContext; - use serde_json::Value; - use std::{collections::BTreeSet, io::Write}; - - fn chronicle_id() -> AuthId { - AuthId::chronicle() - } - - fn chronicle_user_opa_data() -> OpaData { - OpaData::Operation(IdentityContext::new( - AuthId::chronicle(), - Value::default(), - Value::default(), - )) - } - - fn allow_all_users() -> (String, String) { - let policy_name = "allow_transactions".to_string(); - let entrypoint = "allow_transactions/allowed_users".to_string(); - (policy_name, entrypoint) - } - - fn anonymous_user() -> AuthId { - AuthId::anonymous() - } - - fn anonymous_user_opa_data() -> OpaData { - OpaData::Operation(IdentityContext::new( - AuthId::anonymous(), - Value::default(), - Value::default(), - )) - } - - fn jwt_user() -> AuthId { - let claims = crate::identity::JwtClaims( - serde_json::json!({ - "sub": "abcdef", - }) - .as_object() - .unwrap() - .to_owned(), - ); - AuthId::from_jwt_claims(&claims, &BTreeSet::from(["sub".to_string()])).unwrap() - } - - fn jwt_user_opa_data() -> OpaData { - OpaData::Operation(IdentityContext::new( - jwt_user(), - Value::default(), - Value::default(), - )) - } - - #[test] - fn policy_loader_invalid_rule() { - let (_policy, entrypoint) = allow_all_users(); - let invalid_rule = "a_rule_that_does_not_exist"; - match CliPolicyLoader::from_embedded_policy(invalid_rule, &entrypoint) { - Err(e) => { - insta::assert_snapshot!(e.to_string(), @"Policy not found: a_rule_that_does_not_exist") - } - _ => panic!("expected error"), - } - } - - #[tokio::test] - async fn opa_executor_allow_chronicle_users() -> Result<(), OpaExecutorError> { - let (policy, entrypoint) = allow_all_users(); - let loader = CliPolicyLoader::from_embedded_policy(&policy, &entrypoint)?; - let mut executor = WasmtimeOpaExecutor::from_loader(&loader).unwrap(); - assert!(executor - .evaluate(&chronicle_id(), &chronicle_user_opa_data()) - .await - .is_ok()); - Ok(()) - } - - #[tokio::test] - async fn opa_executor_allow_anonymous_users() -> Result<(), OpaExecutorError> { - let (policy, entrypoint) = allow_all_users(); - let loader = CliPolicyLoader::from_embedded_policy(&policy, &entrypoint)?; - let mut executor = WasmtimeOpaExecutor::from_loader(&loader).unwrap(); - executor - .evaluate(&anonymous_user(), &anonymous_user_opa_data()) - .await - .unwrap(); - Ok(()) - } - - #[tokio::test] - async fn opa_executor_allow_jwt_users() -> Result<(), OpaExecutorError> { - let (policy, entrypoint) = allow_all_users(); - let loader = CliPolicyLoader::from_embedded_policy(&policy, &entrypoint)?; - let mut executor = WasmtimeOpaExecutor::from_loader(&loader)?; - assert!(executor - .evaluate(&jwt_user(), &jwt_user_opa_data()) - .await - .is_ok()); - Ok(()) - } - - const BUNDLE_FILE: &str = "bundle.tar.gz"; - - fn embedded_policy_bundle() -> Result, PolicyLoaderError> { - EmbeddedOpaPolicies::get(BUNDLE_FILE) - .map(|file| file.data.to_vec()) - .ok_or(PolicyLoaderError::EmbeddedOpaPolicies) - } - - #[tokio::test] - async fn test_load_policy_from_http_url() { - let embedded_bundle = embedded_policy_bundle().unwrap(); - let (rule, entrypoint) = allow_all_users(); - - // Create a temporary HTTP server that serves a policy bundle - let mut server = mockito::Server::new_async().await; - - // Start the mock server and define the response - let _m = server - .mock("GET", "/bundle.tar.gz") - .with_body(&embedded_bundle) - .create_async() - .await; - - // Create the URL policy loader - let mut loader = UrlPolicyLoader::new( - &format!("{}/bundle.tar.gz", server.url()), - &rule, - &entrypoint, - ); - - // Load the policy - let result = loader.load_policy().await; - assert!(result.is_ok()); - - let bundle = Bundle::from_bytes(&embedded_bundle).unwrap(); - - // Extract the policy from the bundle we embedded in the binary - let policy_from_embedded_bundle = bundle - .wasm_policies - .iter() - .find(|p| p.entrypoint == rule) - .map(|p| p.bytes.as_ref()) - .ok_or(PolicyLoaderError::MissingPolicy(rule.to_string())) - .unwrap(); - - // Get the loaded policy from the url - let policy_from_url = loader.get_policy(); - - assert_eq!(&policy_from_url, &policy_from_embedded_bundle); - } - - #[tokio::test] - async fn test_load_policy_from_file_url() { - let embedded_bundle = embedded_policy_bundle().unwrap(); - let (rule, entrypoint) = allow_all_users(); - - let temp_dir = tempfile::tempdir().unwrap(); - let policy_path = temp_dir.path().join("bundle.tar.gz"); - let mut file = std::fs::File::create(&policy_path).unwrap(); - file.write_all(&embedded_bundle).unwrap(); - - // Create the file URL policy loader - let file_url = format!("file://{}", policy_path.to_string_lossy()); - let mut loader = UrlPolicyLoader::new(&file_url, &rule, &entrypoint); - - // Load the policy - let result = loader.load_policy().await; - assert!(result.is_ok()); - - let bundle = Bundle::from_bytes(&embedded_bundle).unwrap(); - - // Extract the policy from the bundle we embedded in the binary - let policy_from_embedded_bundle = bundle - .wasm_policies - .iter() - .find(|p| p.entrypoint == rule) - .map(|p| p.bytes.as_ref()) - .ok_or(PolicyLoaderError::MissingPolicy(rule.to_string())) - .unwrap(); - - // Get the loaded policy from the file URL - let policy_from_file_url = loader.get_policy(); - - assert_eq!(policy_from_embedded_bundle, policy_from_file_url); - } - - #[tokio::test] - async fn test_load_policy_from_bare_path() { - let embedded_bundle = embedded_policy_bundle().unwrap(); - let (rule, entrypoint) = allow_all_users(); - - let temp_dir = tempfile::tempdir().unwrap(); - let policy_path = temp_dir.path().join("bundle.tar.gz"); - let mut file = std::fs::File::create(&policy_path).unwrap(); - file.write_all(&embedded_bundle).unwrap(); - - // Create the bare path policy loader - let mut loader = UrlPolicyLoader::new(&policy_path.to_string_lossy(), &rule, &entrypoint); - - // Load the policy - let result = loader.load_policy().await; - assert!(result.is_ok()); - - let bundle = Bundle::from_bytes(&embedded_bundle).unwrap(); - - // Extract the policy from the bundle we embedded in the binary - let policy_from_embedded_bundle = bundle - .wasm_policies - .iter() - .find(|p| p.entrypoint == rule) - .map(|p| p.bytes.as_ref()) - .ok_or(PolicyLoaderError::MissingPolicy(rule.to_string())) - .unwrap(); - - // Get the loaded policy from the url - let policy_from_bare_path_url = loader.get_policy(); - - assert_eq!(policy_from_embedded_bundle, policy_from_bare_path_url); - } -} diff --git a/crates/common/src/prov/id/diesel_bindings.rs b/crates/common/src/prov/id/diesel_bindings.rs new file mode 100644 index 000000000..ddb472c4e --- /dev/null +++ b/crates/common/src/prov/id/diesel_bindings.rs @@ -0,0 +1,47 @@ +use super::*; +use diesel::{ + backend::Backend, + deserialize::FromSql, + serialize::{Output, ToSql}, + sql_types::Text, +}; + +impl ToSql for Role +where + DB: Backend, + String: ToSql, +{ + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result { + self.0.to_sql(out) + } +} + +impl FromSql for Role +where + DB: Backend, + String: FromSql, +{ + fn from_sql(bytes: ::RawValue<'_>) -> diesel::deserialize::Result { + Ok(Self(String::from_sql(bytes)?)) + } +} + +impl ToSql for ExternalId +where + DB: Backend, + String: ToSql, +{ + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result { + self.0.to_sql(out) + } +} + +impl FromSql for ExternalId +where + DB: Backend, + String: FromSql, +{ + fn from_sql(bytes: ::RawValue<'_>) -> diesel::deserialize::Result { + Ok(Self(String::from_sql(bytes)?)) + } +} diff --git a/crates/common/src/prov/id/graphlql_scalars.rs b/crates/common/src/prov/id/graphlql_scalars.rs index ba889160f..47a78a6d9 100644 --- a/crates/common/src/prov/id/graphlql_scalars.rs +++ b/crates/common/src/prov/id/graphlql_scalars.rs @@ -9,74 +9,74 @@ async_graphql::scalar!(ChronicleJSON); /// Derived from an `Activity`'s or `Agent`'s or `Entity`'s subtype. /// The built-in GraphQL field `__TypeName` should be used for union queries. impl ScalarType for DomaintypeId { - fn parse(value: Value) -> InputValueResult { - if let Value::String(value) = &value { - // Parse the integer value - Ok(DomaintypeId::try_from(Iri::from_str(value)?)?) - } else { - // If the type does not match - Err(InputValueError::expected_type(value)) - } - } + fn parse(value: Value) -> InputValueResult { + if let Value::String(value) = value { + // Parse the integer value + Ok(DomaintypeId::try_from(value)?) + } else { + // If the type does not match + Err(InputValueError::expected_type(value)) + } + } - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } + fn to_value(&self) -> Value { + Value::String(self.to_string()) + } } #[Scalar(name = "EntityID")] /// This is derived from an `Entity`'s externalId, but clients /// should not attempt to synthesize it themselves. impl ScalarType for EntityId { - fn parse(value: Value) -> InputValueResult { - if let Value::String(value) = &value { - // Parse the integer value - Ok(EntityId::try_from(Iri::from_str(value)?)?) - } else { - // If the type does not match - Err(InputValueError::expected_type(value)) - } - } + fn parse(value: Value) -> InputValueResult { + if let Value::String(value) = value { + // Parse the integer value + Ok(EntityId::try_from(value)?) + } else { + // If the type does not match + Err(InputValueError::expected_type(value)) + } + } - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } + fn to_value(&self) -> Value { + Value::String(self.to_string()) + } } #[Scalar(name = "AgentID")] /// This is derived from an `Agent`'s externalId, but clients /// should not attempt to synthesize it themselves. impl ScalarType for AgentId { - fn parse(value: Value) -> InputValueResult { - if let Value::String(value) = &value { - // Parse the integer value - Ok(AgentId::try_from(Iri::from_str(value)?)?) - } else { - // If the type does not match - Err(InputValueError::expected_type(value)) - } - } + fn parse(value: Value) -> InputValueResult { + if let Value::String(value) = value { + // Parse the integer value + Ok(AgentId::try_from(value)?) + } else { + // If the type does not match + Err(InputValueError::expected_type(value)) + } + } - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } + fn to_value(&self) -> Value { + Value::String(self.to_string()) + } } #[Scalar(name = "ActivityID")] /// This is derived from an `Activity`'s externalId, but clients /// should not attempt to synthesize it themselves. impl ScalarType for ActivityId { - fn parse(value: Value) -> InputValueResult { - if let Value::String(value) = &value { - // Parse the integer value - Ok(ActivityId::try_from(Iri::from_str(value)?)?) - } else { - // If the type does not match - Err(InputValueError::expected_type(value)) - } - } + fn parse(value: Value) -> InputValueResult { + if let Value::String(value) = value { + // Parse the integer value + Ok(ActivityId::try_from(value)?) + } else { + // If the type does not match + Err(InputValueError::expected_type(value)) + } + } - fn to_value(&self) -> Value { - Value::String(self.to_string()) - } + fn to_value(&self) -> Value { + Value::String(self.to_string()) + } } diff --git a/crates/common/src/prov/id/mod.rs b/crates/common/src/prov/id/mod.rs index d3fd4b18a..8e3492f5a 100644 --- a/crates/common/src/prov/id/mod.rs +++ b/crates/common/src/prov/id/mod.rs @@ -1,1007 +1,1068 @@ +#[cfg(feature = "graphql-bindings")] mod graphlql_scalars; +#[cfg(feature = "graphql-bindings")] use async_graphql::OneofObject; -use frame_support::storage::types::EncodeLikeTuple; +#[cfg(feature = "graphql-bindings")] pub use graphlql_scalars::*; -use parity_scale_codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; + +use iri_string::types::{IriAbsoluteString, UriAbsoluteString, UriRelativeStr}; +use parity_scale_codec::{Decode, Encode}; use scale_info::{build::Fields, Path, Type, TypeInfo}; use tracing::trace; -use std::{fmt::Display, str::FromStr}; +#[cfg(feature = "diesel-bindings")] +mod diesel_bindings; + +#[cfg(not(feature = "std"))] +use parity_scale_codec::{alloc::string::String, alloc::vec::Vec}; -use diesel::{ - backend::Backend, - deserialize::FromSql, - serialize::{Output, ToSql}, - sql_types::Text, - AsExpression, FromSqlRow, +#[cfg(not(feature = "std"))] +use scale_info::{ + prelude::borrow::ToOwned, prelude::string::ToString, prelude::sync::Arc, prelude::*, }; -use iref::{Iri, IriRefBuf}; + +#[cfg(feature = "diesel-bindings")] +use diesel::{AsExpression, FromSqlRow}; use serde::Serialize; use uuid::Uuid; use super::vocab::Chronicle; - -custom_error::custom_error! {pub ParseIriError - NotAnIri {source: iref::Error } = "Invalid IRI", - UnparsableIri {iri: IriRefBuf} = "Unparsable Chronicle IRI", - UnparsableUuid {source: uuid::Error } = "Unparsable UUID", - IncorrectIriKind = "Unexpected IRI type", - MissingComponent{component: String} = "Expected {component}", +#[cfg(feature = "std")] +use thiserror::Error; +#[cfg(not(feature = "std"))] +use thiserror_no_std::Error; + +#[derive(Debug, Error)] +pub enum ParseIriError { + #[error("Not an IRI")] + NotAnIri(String), + #[error("Unparsable Chronicle IRI")] + UnparsableIri(String), + #[error("Unparsable UUID")] + UnparsableUuid(uuid::Error), + #[error("Unexpected IRI type")] + IncorrectIriKind(String), + #[error("Expected {component}")] + MissingComponent { component: String }, +} + +// Percent decoded, and has the correct authority +pub struct ProbableChronicleIri(iri_string::types::IriAbsoluteString); + +impl ProbableChronicleIri { + fn from_string(str: String) -> Result { + let uri = iri_string::types::UriAbsoluteString::try_from(str) + .map_err(|e| ParseIriError::NotAnIri(e.into_source()))?; + + Self::from_uri(uri) + } + + fn from_uri(uri: UriAbsoluteString) -> Result { + let iri: IriAbsoluteString = uri.into(); + if iri.authority_str() != Some(Chronicle::PREFIX) + && iri.authority_str() != Some(Chronicle::LONG_PREFIX) + { + return Err(ParseIriError::IncorrectIriKind(iri.to_string())); + } + + Ok(Self(iri)) + } + + fn path_components<'a>(&'a self) -> impl Iterator { + self.0.path_str().split(':') + } +} + +impl core::fmt::Display for ProbableChronicleIri { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0) + } } #[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, - AsExpression, - FromSqlRow, + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, )] -#[diesel(sql_type = diesel::sql_types::Text)] +#[cfg_attr(feature = "diesel-bindings", derive(AsExpression, FromSqlRow))] +#[cfg_attr(feature = "diesel-bindings", diesel(sql_type = diesel::sql_types::Text))] pub struct Role(pub String); -impl Display for Role { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl ToSql for Role -where - DB: Backend, - String: ToSql, -{ - fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result { - self.0.to_sql(out) - } -} - -impl FromSql for Role -where - DB: Backend, - String: FromSql, -{ - fn from_sql(bytes: ::RawValue<'_>) -> diesel::deserialize::Result { - Ok(Self(String::from_sql(bytes)?)) - } +impl core::fmt::Display for Role { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0) + } } impl From for Role where - T: AsRef, + T: AsRef, { - fn from(s: T) -> Self { - Role(s.as_ref().to_owned()) - } + fn from(s: T) -> Self { + Role(s.as_ref().to_owned()) + } } impl Role { - pub fn as_str(&self) -> &str { - &self.0 - } + pub fn as_str(&self) -> &str { + &self.0 + } } impl AsRef for &Role { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } #[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, - AsExpression, - FromSqlRow, + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, )] -#[diesel(sql_type = diesel::sql_types::Text)] +#[cfg_attr(feature = "diesel-bindings", derive(AsExpression, FromSqlRow))] +#[cfg_attr(feature = "diesel-bindings", diesel(sql_type = diesel::sql_types::Text))] pub struct ExternalId(String); -impl Display for ExternalId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl ToSql for ExternalId -where - DB: Backend, - String: ToSql, -{ - fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result { - self.0.to_sql(out) - } -} - -impl FromSql for ExternalId -where - DB: Backend, - String: FromSql, -{ - fn from_sql(bytes: ::RawValue<'_>) -> diesel::deserialize::Result { - Ok(Self(String::from_sql(bytes)?)) - } +impl core::fmt::Display for ExternalId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0) + } } impl From for ExternalId where - T: AsRef, + T: AsRef, { - fn from(s: T) -> Self { - ExternalId(s.as_ref().to_owned()) - } + fn from(s: T) -> Self { + ExternalId(s.as_ref().to_owned()) + } } impl ExternalId { - pub fn as_str(&self) -> &str { - &self.0 - } + pub fn as_str(&self) -> &str { + &self.0 + } } impl AsRef for &ExternalId { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } pub trait ExternalIdPart { - fn external_id_part(&self) -> &ExternalId; + fn external_id_part(&self) -> &ExternalId; } pub trait UuidPart { - fn uuid_part(&self) -> &Uuid; -} - -pub trait SignaturePart { - fn signature_part(&self) -> &str; -} - -pub trait PublicKeyPart { - fn public_key_part(&self) -> &str; + fn uuid_part(&self) -> &Uuid; } /// Transform a chronicle IRI into its compact representation pub trait AsCompact { - fn compact(&self) -> String; + fn compact(&self) -> String; } -impl AsCompact for T { - fn compact(&self) -> String { - self.to_string() - .replace(Chronicle::LONG_PREFIX, Chronicle::PREFIX) - } +impl AsCompact for T { + fn compact(&self) -> String { + self.to_string().replace(Chronicle::LONG_PREFIX, Chronicle::PREFIX) + } } /// Transform a chronicle IRI into its long-form representation pub trait FromCompact { - fn de_compact(&self) -> String; + fn de_compact(&self) -> String; } -impl FromCompact for T { - fn de_compact(&self) -> String { - self.to_string() - .replace(Chronicle::PREFIX, Chronicle::LONG_PREFIX) - } +impl FromCompact for T { + fn de_compact(&self) -> String { + self.to_string().replace(Chronicle::PREFIX, Chronicle::LONG_PREFIX) + } } #[derive( - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, - PartialEq, - Eq, - Hash, - Debug, - Clone, - Ord, - PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, + PartialEq, + Eq, + Hash, + Debug, + Clone, + Ord, + PartialOrd, )] pub enum ChronicleIri { - Namespace(NamespaceId), - Domaintype(DomaintypeId), - Entity(EntityId), - Agent(AgentId), - Activity(ActivityId), - Association(AssociationId), - Attribution(AttributionId), - Delegation(DelegationId), -} - -impl MaxEncodedLen for ChronicleIri { - fn max_encoded_len() -> usize { - 2048usize - } -} - -impl Display for ChronicleIri { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ChronicleIri::Namespace(id) => write!(f, "{id}"), - ChronicleIri::Domaintype(id) => write!(f, "{id}"), - ChronicleIri::Entity(id) => write!(f, "{id}"), - ChronicleIri::Agent(id) => write!(f, "{id}"), - ChronicleIri::Activity(id) => write!(f, "{id}"), - ChronicleIri::Association(id) => write!(f, "{id}"), - ChronicleIri::Attribution(id) => write!(f, "{id}"), - ChronicleIri::Delegation(id) => write!(f, "{id}"), - } - } + Namespace(NamespaceId), + Domaintype(DomaintypeId), + Entity(EntityId), + Agent(AgentId), + Activity(ActivityId), + Association(AssociationId), + Attribution(AttributionId), + Delegation(DelegationId), +} + +impl parity_scale_codec::MaxEncodedLen for ChronicleIri { + fn max_encoded_len() -> usize { + 2048usize + } +} + +impl core::fmt::Display for ChronicleIri { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ChronicleIri::Namespace(id) => write!(f, "{id}"), + ChronicleIri::Domaintype(id) => write!(f, "{id}"), + ChronicleIri::Entity(id) => write!(f, "{id}"), + ChronicleIri::Agent(id) => write!(f, "{id}"), + ChronicleIri::Activity(id) => write!(f, "{id}"), + ChronicleIri::Association(id) => write!(f, "{id}"), + ChronicleIri::Attribution(id) => write!(f, "{id}"), + ChronicleIri::Delegation(id) => write!(f, "{id}"), + } + } } impl From for ChronicleIri { - fn from(val: NamespaceId) -> Self { - ChronicleIri::Namespace(val) - } + fn from(val: NamespaceId) -> Self { + ChronicleIri::Namespace(val) + } } impl From for ChronicleIri { - fn from(val: DomaintypeId) -> Self { - ChronicleIri::Domaintype(val) - } + fn from(val: DomaintypeId) -> Self { + ChronicleIri::Domaintype(val) + } } impl From for ChronicleIri { - fn from(val: EntityId) -> Self { - ChronicleIri::Entity(val) - } + fn from(val: EntityId) -> Self { + ChronicleIri::Entity(val) + } } impl From for ChronicleIri { - fn from(val: AgentId) -> Self { - ChronicleIri::Agent(val) - } + fn from(val: AgentId) -> Self { + ChronicleIri::Agent(val) + } } impl From for ChronicleIri { - fn from(val: ActivityId) -> Self { - ChronicleIri::Activity(val) - } + fn from(val: ActivityId) -> Self { + ChronicleIri::Activity(val) + } } impl From for ChronicleIri { - fn from(val: AssociationId) -> Self { - ChronicleIri::Association(val) - } + fn from(val: AssociationId) -> Self { + ChronicleIri::Association(val) + } } impl From for ChronicleIri { - fn from(val: AttributionId) -> Self { - ChronicleIri::Attribution(val) - } + fn from(val: AttributionId) -> Self { + ChronicleIri::Attribution(val) + } } impl From for ChronicleIri { - fn from(val: DelegationId) -> Self { - ChronicleIri::Delegation(val) - } -} - -impl FromStr for ChronicleIri { - type Err = ParseIriError; - - fn from_str(s: &str) -> Result { - trace!(parsing_iri = %s); - //Compacted form, expand - let iri = { - if s.starts_with(Chronicle::PREFIX) { - s.replace(Chronicle::PREFIX, Chronicle::LONG_PREFIX) - } else { - s.to_owned() - } - }; - - let iri = IriRefBuf::from_str(&iri)?; - - match fragment_components(iri.as_iri()?) - .iter() - .map(|x| x.as_str()) - .collect::>() - .as_slice() - { - ["agent", ..] => Ok(AgentId::try_from(iri.as_iri()?)?.into()), - ["ns", ..] => Ok(NamespaceId::try_from(iri.as_iri()?)?.into()), - ["activity", ..] => Ok(ActivityId::try_from(iri.as_iri()?)?.into()), - ["entity", ..] => Ok(EntityId::try_from(iri.as_iri()?)?.into()), - ["domaintype", ..] => Ok(DomaintypeId::try_from(iri.as_iri()?)?.into()), - ["association", ..] => Ok(AssociationId::try_from(iri.as_iri()?)?.into()), - ["attribution", ..] => Ok(AttributionId::try_from(iri.as_iri()?)?.into()), - ["delegation", ..] => Ok(DelegationId::try_from(iri.as_iri()?)?.into()), - _ => Err(ParseIriError::UnparsableIri { iri }), - } - } + fn from(val: DelegationId) -> Self { + ChronicleIri::Delegation(val) + } +} + +impl core::str::FromStr for ChronicleIri { + type Err = ParseIriError; + + fn from_str(s: &str) -> Result { + trace!(parsing_iri = %s); + //Compacted form, expand + + let iri = ProbableChronicleIri::from_string(s.to_owned())?; + + //TODO: this just needs to extract the first path component + match iri.path_components().collect::>().as_slice() { + ["agent", ..] => Ok(AgentId::try_from(iri)?.into()), + ["ns", ..] => Ok(NamespaceId::try_from(iri)?.into()), + ["activity", ..] => Ok(ActivityId::try_from(iri)?.into()), + ["entity", ..] => Ok(EntityId::try_from(iri)?.into()), + ["domaintype", ..] => Ok(DomaintypeId::try_from(iri)?.into()), + ["association", ..] => Ok(AssociationId::try_from(iri)?.into()), + ["attribution", ..] => Ok(AttributionId::try_from(iri)?.into()), + ["delegation", ..] => Ok(DelegationId::try_from(iri)?.into()), + _ => Err(ParseIriError::UnparsableIri(s.to_string())), + } + } } impl ChronicleIri { - // Coerce this to a `NamespaceId`, if possible - pub fn namespace(self) -> Result { - match self { - ChronicleIri::Namespace(ns) => Ok(ns), - _ => Err(ParseIriError::IncorrectIriKind), - } - } + // Coerce this to a `NamespaceId`, if possible + pub fn namespace(self) -> Result { + match self { + ChronicleIri::Namespace(ns) => Ok(ns), + _ => Err(ParseIriError::IncorrectIriKind(self.to_string())), + } + } } #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] pub struct ChronicleJSON(pub serde_json::Value); -fn fragment_components(iri: Iri) -> Vec { - match iri.fragment() { - Some(fragment) => fragment - .as_str() - .split(':') - .map(|s| { - percent_encoding::percent_decode_str(s) - .decode_utf8_lossy() - .to_string() - }) - .collect(), - None => vec![], - } -} - fn optional_component(external_id: &str, component: &str) -> Result, ParseIriError> { - let kv = format!("{external_id}="); - if !component.starts_with(&*kv) { - return Err(ParseIriError::MissingComponent { - component: external_id.to_string(), - }); - } + let kv = format!("{external_id}="); + if !component.starts_with(&*kv) { + return Err(ParseIriError::MissingComponent { component: external_id.to_string() }); + } - match component.replace(&*kv, "") { - s if s.is_empty() => Ok(None), - s => Ok(Some(s)), - } + match component.replace(&*kv, "") { + s if s.is_empty() => Ok(None), + s => Ok(Some(s)), + } } // A composite identifier of agent, activity and role #[derive( - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, - PartialEq, - Eq, - Hash, - Debug, - Clone, - Ord, - PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, + PartialEq, + Eq, + Hash, + Debug, + Clone, + Ord, + PartialOrd, )] pub struct DelegationId { - delegate: ExternalId, - responsible: ExternalId, - activity: Option, - role: Option, + delegate: ExternalId, + responsible: ExternalId, + activity: Option, + role: Option, } -impl Display for DelegationId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(Into::::into(self).as_str()) - } +impl core::fmt::Display for DelegationId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(Into::::into(self).as_str()) + } } impl DelegationId { - pub fn from_component_ids( - delegate: &AgentId, - responsible: &AgentId, - activity: Option<&ActivityId>, - role: Option>, - ) -> Self { - Self { - delegate: delegate.external_id_part().clone(), - responsible: responsible.external_id_part().clone(), - activity: activity.map(|x| ExternalIdPart::external_id_part(x).to_owned()), - role: role.map(|x| Role::from(x.as_ref())), - } - } - - pub fn delegate(&self) -> AgentId { - AgentId::from_external_id(&self.delegate) - } - - pub fn responsible(&self) -> AgentId { - AgentId::from_external_id(&self.responsible) - } - - pub fn activity(&self) -> Option { - self.activity.as_ref().map(ActivityId::from_external_id) - } - - pub fn role(&self) -> &Option { - &self.role - } -} - -impl<'a> TryFrom> for DelegationId { - type Error = ParseIriError; - - fn try_from(value: Iri) -> Result { - let de_compacted = value.de_compact(); - - let value = Iri::from_str(&de_compacted)?; - - match fragment_components(value).as_slice() { - [_, delegate, responsible, role, activity] => Ok(Self { - delegate: ExternalId::from(delegate), - responsible: ExternalId::from(responsible), - role: optional_component("role", role)?.map(Role::from), - activity: optional_component("activity", activity)?.map(ExternalId::from), - }), - - _ => Err(ParseIriError::UnparsableIri { iri: value.into() }), - } - } -} - -impl From<&DelegationId> for IriRefBuf { - fn from(val: &DelegationId) -> Self { - Chronicle::delegation( - &AgentId::from_external_id(&val.delegate), - &AgentId::from_external_id(&val.responsible), - &val.activity() - .map(|n| ActivityId::from_external_id(n.external_id_part())), - &val.role, - ) - .into() - } + pub fn from_component_ids( + delegate: &AgentId, + responsible: &AgentId, + activity: Option<&ActivityId>, + role: Option>, + ) -> Self { + Self { + delegate: delegate.external_id_part().clone(), + responsible: responsible.external_id_part().clone(), + activity: activity.map(|x| ExternalIdPart::external_id_part(x).to_owned()), + role: role.map(|x| Role::from(x.as_ref())), + } + } + + pub fn delegate(&self) -> AgentId { + AgentId::from_external_id(&self.delegate) + } + + pub fn responsible(&self) -> AgentId { + AgentId::from_external_id(&self.responsible) + } + + pub fn activity(&self) -> Option { + self.activity.as_ref().map(ActivityId::from_external_id) + } + + pub fn role(&self) -> &Option { + &self.role + } +} + +impl TryFrom for DelegationId { + type Error = ParseIriError; + + fn try_from(value: String) -> Result { + ProbableChronicleIri::from_string(value)?.try_into() + } +} + +impl TryFrom for DelegationId { + type Error = ParseIriError; + + fn try_from(value: UriAbsoluteString) -> Result { + ProbableChronicleIri::from_uri(value)?.try_into() + } +} + +impl TryFrom for DelegationId { + type Error = ParseIriError; + + fn try_from(iri: ProbableChronicleIri) -> Result { + match iri.path_components().collect::>().as_slice() { + [_, delegate, responsible, role, activity] => Ok(Self { + delegate: ExternalId::from(delegate), + responsible: ExternalId::from(responsible), + role: optional_component("role", role)?.map(Role::from), + activity: optional_component("activity", activity)?.map(ExternalId::from), + }), + + _ => Err(ParseIriError::UnparsableIri(iri.to_string())), + } + } +} + +impl From<&DelegationId> for UriAbsoluteString { + fn from(val: &DelegationId) -> Self { + Chronicle::delegation( + &AgentId::from_external_id(&val.delegate), + &AgentId::from_external_id(&val.responsible), + &val.activity().map(|n| ActivityId::from_external_id(n.external_id_part())), + &val.role, + ) + .into() + } } // A composite identifier of agent, activity and role #[derive( - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, - PartialEq, - Eq, - Hash, - Debug, - Clone, - Ord, - PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, + PartialEq, + Eq, + Hash, + Debug, + Clone, + Ord, + PartialOrd, )] pub struct AssociationId { - agent: ExternalId, - activity: ExternalId, - role: Option, + agent: ExternalId, + activity: ExternalId, + role: Option, } -impl Display for AssociationId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(Into::::into(self).as_str()) - } +impl core::fmt::Display for AssociationId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(Into::::into(self).as_str()) + } } impl AssociationId { - pub fn from_component_ids( - agent: &AgentId, - activity: &ActivityId, - role: Option>, - ) -> Self { - Self { - agent: agent.external_id_part().clone(), - activity: activity.external_id_part().clone(), - role: role.map(|x| Role::from(x.as_ref())), - } - } - - pub fn agent(&self) -> AgentId { - AgentId::from_external_id(&self.agent) - } - - pub fn activity(&self) -> ActivityId { - ActivityId::from_external_id(&self.activity) - } -} - -impl<'a> TryFrom> for AssociationId { - type Error = ParseIriError; - - fn try_from(value: Iri) -> Result { - let de_compacted = value.de_compact(); - - let value = Iri::from_str(&de_compacted)?; - - match fragment_components(value).as_slice() { - [_, agent, activity, role] => Ok(Self { - agent: ExternalId::from(agent), - activity: ExternalId::from(activity), - role: optional_component("role", role)?.map(Role::from), - }), - - _ => Err(ParseIriError::UnparsableIri { iri: value.into() }), - } - } -} - -impl From<&AssociationId> for IriRefBuf { - fn from(val: &AssociationId) -> Self { - Chronicle::association( - &AgentId::from_external_id(&val.agent), - &ActivityId::from_external_id(&val.activity), - &val.role, - ) - .into() - } + pub fn from_component_ids( + agent: &AgentId, + activity: &ActivityId, + role: Option>, + ) -> Self { + Self { + agent: agent.external_id_part().clone(), + activity: activity.external_id_part().clone(), + role: role.map(|x| Role::from(x.as_ref())), + } + } + + pub fn agent(&self) -> AgentId { + AgentId::from_external_id(&self.agent) + } + + pub fn activity(&self) -> ActivityId { + ActivityId::from_external_id(&self.activity) + } +} + +impl TryFrom for AssociationId { + type Error = ParseIriError; + + fn try_from(value: String) -> Result { + ProbableChronicleIri::from_string(value)?.try_into() + } +} + +impl TryFrom for AssociationId { + type Error = ParseIriError; + + fn try_from(value: UriAbsoluteString) -> Result { + ProbableChronicleIri::from_uri(value)?.try_into() + } +} + +impl TryFrom for AssociationId { + type Error = ParseIriError; + + fn try_from(iri: ProbableChronicleIri) -> Result { + match iri.path_components().collect::>().as_slice() { + [_, agent, activity, role] => Ok(Self { + agent: ExternalId::from(agent), + activity: ExternalId::from(activity), + role: optional_component("role", role)?.map(Role::from), + }), + + _ => Err(ParseIriError::UnparsableIri(iri.to_string())), + } + } +} + +impl From<&AssociationId> for UriAbsoluteString { + fn from(val: &AssociationId) -> Self { + Chronicle::association( + &AgentId::from_external_id(&val.agent), + &ActivityId::from_external_id(&val.activity), + &val.role, + ) + .into() + } } // A composite identifier of agent, entity, and role #[derive( - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, - PartialEq, - Eq, - Hash, - Debug, - Clone, - Ord, - PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, + PartialEq, + Eq, + Hash, + Debug, + Clone, + Ord, + PartialOrd, )] pub struct AttributionId { - agent: ExternalId, - entity: ExternalId, - role: Option, + agent: ExternalId, + entity: ExternalId, + role: Option, } -impl Display for AttributionId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(Into::::into(self).as_str()) - } +impl core::fmt::Display for AttributionId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(Into::::into(self).as_str()) + } } impl AttributionId { - pub fn from_component_ids( - agent: &AgentId, - entity: &EntityId, - role: Option>, - ) -> Self { - Self { - agent: agent.external_id_part().clone(), - entity: entity.external_id_part().clone(), - role: role.map(|x| Role::from(x.as_ref())), - } - } - - pub fn agent(&self) -> AgentId { - AgentId::from_external_id(&self.agent) - } - - pub fn entity(&self) -> EntityId { - EntityId::from_external_id(&self.entity) - } -} - -impl<'a> TryFrom> for AttributionId { - type Error = ParseIriError; - - fn try_from(value: Iri) -> Result { - let de_compacted = value.de_compact(); - - let value = Iri::from_str(&de_compacted)?; - - match fragment_components(value).as_slice() { - [_, agent, entity, role] => Ok(Self { - agent: ExternalId::from(agent), - entity: ExternalId::from(entity), - role: optional_component("role", role)?.map(Role::from), - }), - - _ => Err(ParseIriError::UnparsableIri { iri: value.into() }), - } - } -} - -impl From<&AttributionId> for IriRefBuf { - fn from(val: &AttributionId) -> Self { - Chronicle::attribution( - &AgentId::from_external_id(&val.agent), - &EntityId::from_external_id(&val.entity), - &val.role, - ) - .into() - } + pub fn from_component_ids( + agent: &AgentId, + entity: &EntityId, + role: Option>, + ) -> Self { + Self { + agent: agent.external_id_part().clone(), + entity: entity.external_id_part().clone(), + role: role.map(|x| Role::from(x.as_ref())), + } + } + + pub fn agent(&self) -> AgentId { + AgentId::from_external_id(&self.agent) + } + + pub fn entity(&self) -> EntityId { + EntityId::from_external_id(&self.entity) + } +} + +impl TryFrom for AttributionId { + type Error = ParseIriError; + + fn try_from(value: String) -> Result { + ProbableChronicleIri::from_string(value)?.try_into() + } +} + +impl TryFrom for AttributionId { + type Error = ParseIriError; + + fn try_from(value: UriAbsoluteString) -> Result { + ProbableChronicleIri::from_uri(value)?.try_into() + } +} + +impl TryFrom for AttributionId { + type Error = ParseIriError; + + fn try_from(iri: ProbableChronicleIri) -> Result { + match iri.path_components().collect::>().as_slice() { + [_, agent, entity, role] => Ok(Self { + agent: ExternalId::from(agent), + entity: ExternalId::from(entity), + role: optional_component("role", role)?.map(Role::from), + }), + + _ => Err(ParseIriError::UnparsableIri(iri.to_string())), + } + } +} + +impl From<&AttributionId> for UriAbsoluteString { + fn from(val: &AttributionId) -> Self { + Chronicle::attribution( + &AgentId::from_external_id(&val.agent), + &EntityId::from_external_id(&val.entity), + &val.role, + ) + .into() + } } #[derive( - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, - PartialEq, - Eq, - Hash, - Debug, - Clone, - Ord, - PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, + PartialEq, + Eq, + Hash, + Debug, + Clone, + Ord, + PartialOrd, )] pub struct DomaintypeId(ExternalId); -impl Display for DomaintypeId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(Into::::into(self).as_str()) - } +impl core::fmt::Display for DomaintypeId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(Into::::into(self).as_str()) + } } impl ExternalIdPart for DomaintypeId { - fn external_id_part(&self) -> &ExternalId { - &self.0 - } + fn external_id_part(&self) -> &ExternalId { + &self.0 + } } impl DomaintypeId { - pub fn from_external_id(external_id: impl AsRef) -> Self { - Self(external_id.as_ref().into()) - } + pub fn from_external_id(external_id: impl AsRef) -> Self { + Self(external_id.as_ref().into()) + } } -impl<'a> TryFrom> for DomaintypeId { - type Error = ParseIriError; +impl TryFrom for DomaintypeId { + type Error = ParseIriError; - fn try_from(value: Iri) -> Result { - let de_compacted = value.de_compact(); + fn try_from(value: String) -> Result { + ProbableChronicleIri::from_string(value)?.try_into() + } +} - let value = Iri::from_str(&de_compacted)?; +impl TryFrom for DomaintypeId { + type Error = ParseIriError; - match fragment_components(value).as_slice() { - [_, external_id] => Ok(Self(ExternalId::from(external_id.as_str()))), - _ => Err(ParseIriError::UnparsableIri { iri: value.into() }), - } - } + fn try_from(value: UriAbsoluteString) -> Result { + ProbableChronicleIri::from_uri(value)?.try_into() + } } -impl From<&DomaintypeId> for IriRefBuf { - fn from(val: &DomaintypeId) -> Self { - Chronicle::domaintype(&val.0).into() - } +impl TryFrom for DomaintypeId { + type Error = ParseIriError; + + fn try_from(iri: ProbableChronicleIri) -> Result { + match iri.path_components().collect::>().as_slice() { + [_, external_id] => Ok(Self(ExternalId::from(external_id))), + _ => Err(ParseIriError::UnparsableIri(iri.to_string())), + } + } +} + +impl From<&DomaintypeId> for UriAbsoluteString { + fn from(val: &DomaintypeId) -> Self { + Chronicle::domaintype(&val.0).into() + } } #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] pub struct UuidWrapper(Uuid); impl From for UuidWrapper { - fn from(uuid: Uuid) -> Self { - Self(uuid) - } + fn from(uuid: Uuid) -> Self { + Self(uuid) + } } impl Encode for UuidWrapper { - fn encode_to(&self, dest: &mut T) { - self.0.as_bytes().encode_to(dest); - } + fn encode_to(&self, dest: &mut T) { + self.0.as_bytes().encode_to(dest); + } } impl Decode for UuidWrapper { - fn decode( - input: &mut I, - ) -> Result { - let uuid_bytes = <[u8; 16]>::decode(input)?; - let uuid = Uuid::from_slice(&uuid_bytes).map_err(|_| "Error decoding UUID")?; - Ok(Self(uuid)) - } + fn decode( + input: &mut I, + ) -> Result { + let uuid_bytes = <[u8; 16]>::decode(input)?; + let uuid = Uuid::from_slice(&uuid_bytes).map_err(|_| "Error decoding UUID")?; + Ok(Self(uuid)) + } } impl TypeInfo for UuidWrapper { - type Identity = Self; - fn type_info() -> Type { - Type::builder() - .path(Path::new("UuidWrapper", module_path!())) - .composite(Fields::unnamed().field(|f| f.ty::<[u8; 16]>().type_name("Uuid"))) - } + type Identity = Self; + fn type_info() -> Type { + Type::builder() + .path(Path::new("UuidWrapper", module_path!())) + .composite(Fields::unnamed().field(|f| f.ty::<[u8; 16]>().type_name("Uuid"))) + } } #[derive( - Serialize, - Deserialize, - Decode, - Encode, - TypeInfo, - PartialEq, - Eq, - Hash, - Debug, - Clone, - Ord, - PartialOrd, + Serialize, + Deserialize, + Decode, + Encode, + TypeInfo, + PartialEq, + Eq, + Hash, + Debug, + Clone, + Ord, + PartialOrd, )] pub struct NamespaceId { - external_id: ExternalId, - uuid: UuidWrapper, + external_id: ExternalId, + uuid: UuidWrapper, } -impl Display for NamespaceId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(Into::::into(self).as_str()) - } +impl core::fmt::Display for NamespaceId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(Into::::into(self).as_str()) + } } impl NamespaceId { - pub fn from_external_id(external_id: impl AsRef, uuid: Uuid) -> Self { - Self { - external_id: external_id.as_ref().into(), - uuid: uuid.into(), - } - } + pub fn from_external_id(external_id: impl AsRef, uuid: Uuid) -> Self { + Self { external_id: external_id.as_ref().into(), uuid: uuid.into() } + } } impl ExternalIdPart for NamespaceId { - fn external_id_part(&self) -> &ExternalId { - &self.external_id - } + fn external_id_part(&self) -> &ExternalId { + &self.external_id + } } impl UuidPart for NamespaceId { - fn uuid_part(&self) -> &Uuid { - &self.uuid.0 - } + fn uuid_part(&self) -> &Uuid { + &self.uuid.0 + } +} + +impl TryFrom for NamespaceId { + type Error = ParseIriError; + + fn try_from(value: String) -> Result { + ProbableChronicleIri::from_string(value)?.try_into() + } } -impl<'a> TryFrom> for NamespaceId { - type Error = ParseIriError; +impl TryFrom for NamespaceId { + type Error = ParseIriError; - fn try_from(value: Iri) -> Result { - let de_compacted = value.de_compact(); + fn try_from(value: UriAbsoluteString) -> Result { + ProbableChronicleIri::from_uri(value)?.try_into() + } +} - let value = Iri::from_str(&de_compacted)?; +impl TryFrom for NamespaceId { + type Error = ParseIriError; - match fragment_components(value).as_slice() { - [_, external_id, uuid] => Ok(Self { - external_id: ExternalId::from(external_id.as_str()), - uuid: Uuid::parse_str(uuid.as_str())?.into(), - }), + fn try_from(iri: ProbableChronicleIri) -> Result { + match iri.path_components().collect::>().as_slice() { + [_, external_id, uuid] => Ok(Self { + external_id: ExternalId::from(external_id), + uuid: Uuid::parse_str(uuid).map_err(ParseIriError::UnparsableUuid)?.into(), + }), - _ => Err(ParseIriError::UnparsableIri { iri: value.into() }), - } - } + _ => Err(ParseIriError::UnparsableIri(iri.to_string())), + } + } } -impl From<&NamespaceId> for IriRefBuf { - fn from(val: &NamespaceId) -> Self { - Chronicle::namespace(&val.external_id, &val.uuid.0).into() - } +impl From<&NamespaceId> for UriAbsoluteString { + fn from(val: &NamespaceId) -> Self { + Chronicle::namespace(&val.external_id, &val.uuid.0).into() + } } #[derive( - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, - PartialEq, - Eq, - Hash, - Debug, - Clone, - Ord, - PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, + PartialEq, + Eq, + Hash, + Debug, + Clone, + Ord, + PartialOrd, )] pub struct EntityId(ExternalId); -impl Display for EntityId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(Into::::into(self).as_str()) - } +impl core::fmt::Display for EntityId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(Into::::into(self).as_str()) + } } impl EntityId { - pub fn from_external_id(external_id: impl AsRef) -> Self { - Self(external_id.as_ref().into()) - } + pub fn from_external_id(external_id: impl AsRef) -> Self { + Self(external_id.as_ref().into()) + } } impl ExternalIdPart for EntityId { - fn external_id_part(&self) -> &ExternalId { - &self.0 - } + fn external_id_part(&self) -> &ExternalId { + &self.0 + } } -impl<'a> TryFrom> for EntityId { - type Error = ParseIriError; +impl TryFrom for EntityId { + type Error = ParseIriError; - fn try_from(value: Iri) -> Result { - let de_compacted = value.de_compact(); + fn try_from(value: String) -> Result { + ProbableChronicleIri::from_string(value)?.try_into() + } +} - let value = Iri::from_str(&de_compacted)?; +impl TryFrom for EntityId { + type Error = ParseIriError; - match fragment_components(value).as_slice() { - [_, external_id] => Ok(Self(ExternalId::from(external_id.as_str()))), + fn try_from(value: UriAbsoluteString) -> Result { + ProbableChronicleIri::from_uri(value)?.try_into() + } +} - _ => Err(ParseIriError::UnparsableIri { iri: value.into() }), - } - } +impl TryFrom for EntityId { + type Error = ParseIriError; + + fn try_from(value: ProbableChronicleIri) -> Result { + match value.path_components().collect::>().as_slice() { + [_, external_id] => Ok(Self(ExternalId::from(external_id))), + + _ => Err(ParseIriError::UnparsableIri(value.to_string())), + } + } } -impl From<&EntityId> for IriRefBuf { - fn from(val: &EntityId) -> Self { - Chronicle::entity(&val.0).into() - } +impl From<&EntityId> for UriAbsoluteString { + fn from(val: &EntityId) -> Self { + Chronicle::entity(&val.0).into() + } } /// Input either a short-form `externalId`, e.g. "agreement", /// or long-form Chronicle `id`, e.g. "chronicle:entity:agreement" -#[derive(OneofObject)] +#[cfg_attr(feature = "graphql-bindings", derive(OneofObject))] pub enum EntityIdOrExternal { - ExternalId(String), - Id(EntityId), + ExternalId(String), + Id(EntityId), } impl From for EntityId { - fn from(input: EntityIdOrExternal) -> Self { - match input { - EntityIdOrExternal::ExternalId(external_id) => Self::from_external_id(external_id), - EntityIdOrExternal::Id(id) => id, - } - } + fn from(input: EntityIdOrExternal) -> Self { + match input { + EntityIdOrExternal::ExternalId(external_id) => Self::from_external_id(external_id), + EntityIdOrExternal::Id(id) => id, + } + } } #[derive( - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, - PartialEq, - Eq, - Hash, - Debug, - Clone, - Ord, - PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, + PartialEq, + Eq, + Hash, + Debug, + Clone, + Ord, + PartialOrd, )] pub struct AgentId(ExternalId); -impl Display for AgentId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(Into::::into(self).as_str()) - } +impl core::fmt::Display for AgentId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(Into::::into(self).as_str()) + } } impl AgentId { - pub fn from_external_id(external_id: impl AsRef) -> Self { - Self(external_id.as_ref().into()) - } + pub fn from_external_id(external_id: impl AsRef) -> Self { + Self(external_id.as_ref().into()) + } } impl ExternalIdPart for AgentId { - fn external_id_part(&self) -> &ExternalId { - &self.0 - } + fn external_id_part(&self) -> &ExternalId { + &self.0 + } } -impl<'a> TryFrom> for AgentId { - type Error = ParseIriError; +impl TryFrom for AgentId { + type Error = ParseIriError; - fn try_from(value: Iri) -> Result { - let de_compacted = value.de_compact(); + fn try_from(value: String) -> Result { + ProbableChronicleIri::from_string(value)?.try_into() + } +} - let value = Iri::from_str(&de_compacted)?; +impl TryFrom for AgentId { + type Error = ParseIriError; + + fn try_from(value: UriAbsoluteString) -> Result { + ProbableChronicleIri::from_uri(value)?.try_into() + } +} - match fragment_components(value).as_slice() { - [_, external_id] => Ok(Self(ExternalId::from(external_id.as_str()))), - _ => Err(ParseIriError::UnparsableIri { iri: value.into() }), - } - } +impl TryFrom for AgentId { + type Error = ParseIriError; + fn try_from(value: ProbableChronicleIri) -> Result { + match value.path_components().collect::>().as_slice() { + [_, external_id] => Ok(Self(ExternalId::from(external_id))), + _ => Err(ParseIriError::UnparsableIri(value.to_string())), + } + } } -impl From<&AgentId> for IriRefBuf { - fn from(val: &AgentId) -> Self { - Chronicle::agent(&val.0).into() - } +impl From<&AgentId> for UriAbsoluteString { + fn from(val: &AgentId) -> Self { + UriAbsoluteString::from(Chronicle::agent(&val.0)) + } } /// Input either a short-form `externalId`, e.g. "bob", /// or long-form Chronicle `id`, e.g. "chronicle:agent:bob" -#[derive(OneofObject)] +#[cfg_attr(feature = "graphql-bindings", derive(OneofObject))] pub enum AgentIdOrExternal { - ExternalId(String), - Id(AgentId), + ExternalId(String), + Id(AgentId), } impl From for AgentId { - fn from(input: AgentIdOrExternal) -> Self { - match input { - AgentIdOrExternal::ExternalId(external_id) => Self::from_external_id(external_id), - AgentIdOrExternal::Id(id) => id, - } - } + fn from(input: AgentIdOrExternal) -> Self { + match input { + AgentIdOrExternal::ExternalId(external_id) => Self::from_external_id(external_id), + AgentIdOrExternal::Id(id) => id, + } + } } #[derive( - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, - PartialEq, - Eq, - Hash, - Debug, - Clone, - Ord, - PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, + PartialEq, + Eq, + Hash, + Debug, + Clone, + Ord, + PartialOrd, )] pub struct ActivityId(ExternalId); -impl Display for ActivityId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(Into::::into(self).as_str()) - } +impl core::fmt::Display for ActivityId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(UriAbsoluteString::from(self).as_str()) + } } impl ActivityId { - pub fn from_external_id(external_id: impl AsRef) -> Self { - Self(external_id.as_ref().into()) - } + pub fn from_external_id(external_id: impl AsRef) -> Self { + Self(external_id.as_ref().into()) + } } impl ExternalIdPart for ActivityId { - fn external_id_part(&self) -> &ExternalId { - &self.0 - } + fn external_id_part(&self) -> &ExternalId { + &self.0 + } } -impl<'a> TryFrom> for ActivityId { - type Error = ParseIriError; +impl TryFrom for ActivityId { + type Error = ParseIriError; - fn try_from(value: Iri) -> Result { - let de_compacted = value.de_compact(); + fn try_from(value: String) -> Result { + ProbableChronicleIri::from_string(value)?.try_into() + } +} + +impl TryFrom for ActivityId { + type Error = ParseIriError; + + fn try_from(value: UriAbsoluteString) -> Result { + ProbableChronicleIri::from_uri(value)?.try_into() + } +} - let value = Iri::from_str(&de_compacted)?; +impl TryFrom for ActivityId { + type Error = ParseIriError; - match fragment_components(value).as_slice() { - [_, external_id] => Ok(Self(ExternalId::from(external_id.as_str()))), + fn try_from(iri: ProbableChronicleIri) -> Result { + match iri.path_components().collect::>().as_slice() { + [_, external_id] => Ok(Self(ExternalId::from(external_id))), - _ => Err(ParseIriError::UnparsableIri { iri: value.into() }), - } - } + _ => Err(ParseIriError::UnparsableIri(iri.to_string())), + } + } } -impl From<&ActivityId> for IriRefBuf { - fn from(val: &ActivityId) -> Self { - Chronicle::activity(&val.0).into() - } +impl From<&ActivityId> for UriAbsoluteString { + fn from(val: &ActivityId) -> Self { + Chronicle::activity(&val.0).into() + } } /// Input either a short-form `externalId`, e.g. "record", /// or long-form Chronicle `id`, e.g. "chronicle:activity:record" -#[derive(OneofObject)] +#[cfg_attr(feature = "graphql-bindings", derive(OneofObject))] pub enum ActivityIdOrExternal { - ExternalId(String), - Id(ActivityId), + ExternalId(String), + Id(ActivityId), } impl From for ActivityId { - fn from(input: ActivityIdOrExternal) -> Self { - match input { - ActivityIdOrExternal::ExternalId(external_id) => Self::from_external_id(external_id), - ActivityIdOrExternal::Id(id) => id, - } - } + fn from(input: ActivityIdOrExternal) -> Self { + match input { + ActivityIdOrExternal::ExternalId(external_id) => Self::from_external_id(external_id), + ActivityIdOrExternal::Id(id) => id, + } + } } /// A `Namespace` ID reserved for Chronicle system use. diff --git a/crates/common/src/prov/model/contradiction.rs b/crates/common/src/prov/model/contradiction.rs index b611aff59..2ef602dfc 100644 --- a/crates/common/src/prov/model/contradiction.rs +++ b/crates/common/src/prov/model/contradiction.rs @@ -1,145 +1,122 @@ -use chrono::{DateTime, NaiveDateTime, Utc}; -use scale_info::{build::Variants, Path, Type, TypeInfo, Variant}; +use chrono::{DateTime, Utc}; +use scale_info::TypeInfo; + +#[cfg(not(feature = "std"))] +use parity_scale_codec::{alloc::string::String, alloc::vec::Vec}; + +#[cfg(not(feature = "std"))] +use scale_info::prelude::*; use crate::{ - attributes::Attribute, - prov::{operations::TimeWrapper, ChronicleIri, NamespaceId}, + attributes::Attribute, + prov::{operations::TimeWrapper, ChronicleIri, NamespaceId}, }; use parity_scale_codec::{Decode, Encode}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Encode, Decode, TypeInfo)] pub struct Contradiction { - pub(crate) id: ChronicleIri, - pub(crate) namespace: NamespaceId, - pub(crate) contradiction: Vec, -} - -impl std::error::Error for Contradiction { - fn source(&self) -> Option<&(dyn custom_error::Error + 'static)> { - None - } - - fn cause(&self) -> Option<&dyn custom_error::Error> { - self.source() - } + pub(crate) id: ChronicleIri, + pub(crate) namespace: NamespaceId, + pub(crate) contradiction: Vec, } -impl std::fmt::Display for Contradiction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Contradiction {{ ")?; - for detail in &self.contradiction { - match detail { - ContradictionDetail::AttributeValueChange { - name, - value, - attempted, - } => { - write!(f, "attribute value change: {name} {value:?} {attempted:?}")?; - } - ContradictionDetail::StartAlteration { value, attempted } => { - write!(f, "start date alteration: {value} {attempted}")?; - } - ContradictionDetail::EndAlteration { value, attempted } => { - write!(f, "end date alteration: {value} {attempted}")?; - } - ContradictionDetail::InvalidRange { start, end } => { - write!(f, "invalid range: {start} {end}")?; - } - } - } - write!(f, " }}") - } +impl core::fmt::Display for Contradiction { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Contradiction {{ ")?; + for detail in &self.contradiction { + match detail { + ContradictionDetail::AttributeValueChange { name, value, attempted } => { + write!(f, "attribute value change: {name} {value:?} {attempted:?}")?; + }, + ContradictionDetail::StartAlteration { value, attempted } => { + write!(f, "start date alteration: {value} {attempted}")?; + }, + ContradictionDetail::EndAlteration { value, attempted } => { + write!(f, "end date alteration: {value} {attempted}")?; + }, + ContradictionDetail::InvalidRange { start, end } => { + write!(f, "invalid range: {start} {end}")?; + }, + } + } + write!(f, " }}") + } } impl Contradiction { - pub fn start_date_alteration( - id: ChronicleIri, - namespace: NamespaceId, - value: DateTime, - attempted: DateTime, - ) -> Self { - Self { - id, - namespace, - contradiction: vec![ContradictionDetail::StartAlteration { - value: value.into(), - attempted: attempted.into(), - }], - } - } + pub fn start_date_alteration( + id: ChronicleIri, + namespace: NamespaceId, + value: DateTime, + attempted: DateTime, + ) -> Self { + Self { + id, + namespace, + contradiction: vec![ContradictionDetail::StartAlteration { + value: value.into(), + attempted: attempted.into(), + }], + } + } - pub fn end_date_alteration( - id: ChronicleIri, - namespace: NamespaceId, - value: DateTime, - attempted: DateTime, - ) -> Self { - Self { - id, - namespace, - contradiction: vec![ContradictionDetail::EndAlteration { - value: value.into(), - attempted: attempted.into(), - }], - } - } + pub fn end_date_alteration( + id: ChronicleIri, + namespace: NamespaceId, + value: DateTime, + attempted: DateTime, + ) -> Self { + Self { + id, + namespace, + contradiction: vec![ContradictionDetail::EndAlteration { + value: value.into(), + attempted: attempted.into(), + }], + } + } - pub fn invalid_range( - id: ChronicleIri, - namespace: NamespaceId, - start: DateTime, - end: DateTime, - ) -> Self { - Self { - id, - namespace, - contradiction: vec![ContradictionDetail::InvalidRange { - start: start.into(), - end: end.into(), - }], - } - } + pub fn invalid_range( + id: ChronicleIri, + namespace: NamespaceId, + start: DateTime, + end: DateTime, + ) -> Self { + Self { + id, + namespace, + contradiction: vec![ContradictionDetail::InvalidRange { + start: start.into(), + end: end.into(), + }], + } + } - pub fn attribute_value_change( - id: ChronicleIri, - namespace: NamespaceId, - changes: Vec<(String, Attribute, Attribute)>, - ) -> Self { - Self { - id, - namespace, - contradiction: changes - .into_iter() - .map( - |(name, value, attempted)| ContradictionDetail::AttributeValueChange { - name, - value, - attempted, - }, - ) - .collect(), - } - } + pub fn attribute_value_change( + id: ChronicleIri, + namespace: NamespaceId, + changes: Vec<(String, Attribute, Attribute)>, + ) -> Self { + Self { + id, + namespace, + contradiction: changes + .into_iter() + .map(|(name, value, attempted)| ContradictionDetail::AttributeValueChange { + name, + value, + attempted, + }) + .collect(), + } + } } #[derive(Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq)] pub enum ContradictionDetail { - AttributeValueChange { - name: String, - value: Attribute, - attempted: Attribute, - }, - StartAlteration { - value: TimeWrapper, - attempted: TimeWrapper, - }, - EndAlteration { - value: TimeWrapper, - attempted: TimeWrapper, - }, - InvalidRange { - start: TimeWrapper, - end: TimeWrapper, - }, + AttributeValueChange { name: String, value: Attribute, attempted: Attribute }, + StartAlteration { value: TimeWrapper, attempted: TimeWrapper }, + EndAlteration { value: TimeWrapper, attempted: TimeWrapper }, + InvalidRange { start: TimeWrapper, end: TimeWrapper }, } diff --git a/crates/common/src/prov/model/from_json_ld.rs b/crates/common/src/prov/model/from_json_ld.rs deleted file mode 100644 index 3dd9e3348..000000000 --- a/crates/common/src/prov/model/from_json_ld.rs +++ /dev/null @@ -1,891 +0,0 @@ -use chrono::{DateTime, Utc}; -use futures::{future::BoxFuture, FutureExt}; -use iref::{AsIri, Iri, IriBuf}; -use json_ld::{ - syntax::IntoJsonWithContextMeta, Indexed, Loader, Node, Profile, RemoteDocument, Term, -}; -use locspan::Meta; -use mime::Mime; -use rdf_types::{vocabulary::no_vocabulary_mut, BlankIdBuf, IriVocabularyMut}; -use serde_json::{json, Value}; -use std::collections::BTreeMap; -use tracing::{error, instrument, trace}; - -use crate::{ - attributes::{Attribute, Attributes}, - prov::{ - operations::{ - ActivityExists, ActivityUses, ActsOnBehalfOf, AgentExists, ChronicleOperation, - CreateNamespace, DerivationType, EndActivity, EntityDerive, EntityExists, - SetAttributes, StartActivity, WasAssociatedWith, WasAttributedTo, WasGeneratedBy, - WasInformedBy, - }, - vocab::{Chronicle, ChronicleOperations, Prov}, - ActivityId, AgentId, DomaintypeId, EntityId, ExternalIdPart, NamespaceId, Role, UuidPart, - }, -}; - -use super::{Activity, Agent, Entity, ProcessorError, ProvModel}; - -pub struct ContextLoader; - -impl Loader for ContextLoader { - type Error = (); - type Output = json_ld::syntax::Value; - - // This is only used to load the context, so we can just return it directly - fn load_with<'b>( - &'b mut self, - vocabulary: &'b mut (impl Sync + Send + IriVocabularyMut), - url: IriBuf, - ) -> BoxFuture, Self::Error>> - where - IriBuf: 'b, - { - use hashbrown::HashSet; - use std::str::FromStr; - let mut profiles = HashSet::new(); - profiles.insert(Profile::new(url.as_iri(), vocabulary)); - trace!("Loading context from {}", url); - async move { - let json = json!({ - "@context": crate::context::PROV.clone() - }); - let value = json_ld::syntax::Value::from_serde_json(json, |_| ()); - Ok(json_ld::RemoteDocument::new_full( - Some(url), - Some(Mime::from_str("application/json").unwrap()), - None, - profiles, - value, - )) - } - .boxed() - } -} - -fn as_json(node: &Node) -> serde_json::Value { - node.clone() - .into_json_meta_with((), no_vocabulary_mut()) - .into_value() - .into() -} - -fn id_from_iri(iri: &dyn AsIri) -> json_ld::Id { - json_ld::Id::Valid(json_ld::ValidId::Iri(iri.as_iri().into())) -} - -fn extract_reference_ids( - iri: &dyn AsIri, - node: &Node, -) -> Result, ProcessorError> { - let ids: Result, _> = node - .get(&id_from_iri(iri)) - .map(|o| { - o.id().ok_or_else(|| ProcessorError::MissingId { - object: as_json(node), - }) - }) - .map(|id| { - id.and_then(|id| { - id.as_iri().ok_or_else(|| ProcessorError::MissingId { - object: as_json(node), - }) - }) - }) - .map(|id| id.map(|id| id.to_owned())) - .collect(); - - ids -} - -fn extract_scalar_prop<'a>( - iri: &dyn AsIri, - node: &'a Node, -) -> Result<&'a Indexed, ()>, ProcessorError> { - if let Some(object) = node.get_any(&id_from_iri(iri)) { - Ok(object) - } else { - Err(ProcessorError::MissingProperty { - iri: iri.as_iri().as_str().to_string(), - object: as_json(node), - }) - } -} - -fn extract_namespace(agent: &Node) -> Result { - Ok(NamespaceId::try_from(Iri::from_str( - extract_scalar_prop(&Chronicle::HasNamespace, agent)? - .id() - .ok_or(ProcessorError::MissingId { - object: as_json(agent), - })? - .as_str(), - )?)?) -} - -impl ProvModel { - pub async fn apply_json_ld_str(&mut self, buf: &str) -> Result<(), ProcessorError> { - self.apply_json_ld(serde_json::from_str(buf)?).await?; - - Ok(()) - } - - pub async fn apply_json_ld_bytes(&mut self, buf: &[u8]) -> Result<(), ProcessorError> { - self.apply_json_ld(serde_json::from_slice(buf)?).await?; - - Ok(()) - } - - /// Take a Json-Ld input document, assuming it is in compact form, expand it and apply the state to the prov model - /// Replace @context with our resource context - /// We rely on reified @types, so subclassing must also include supertypes - #[instrument(level = "trace", skip(self, json))] - pub async fn apply_json_ld(&mut self, json: serde_json::Value) -> Result<(), ProcessorError> { - if let serde_json::Value::Object(mut map) = json { - map.insert( - "@context".to_string(), - serde_json::Value::String("https://btp.works/chr/1.0/c.jsonld".to_string()), - ); - let json = serde_json::Value::Object(map); - - trace!(to_apply_compact=%serde_json::to_string_pretty(&json)?); - - use json_ld::Expand; - let output = json_ld::syntax::Value::from_serde_json(json.clone(), |_| ()) - .expand(&mut ContextLoader) - .await - .map_err(|e| ProcessorError::Expansion { - inner: format!("{e:?}"), - })?; - - for o in output.into_value().into_objects() { - let o = o - .value() - .inner() - .as_node() - .ok_or(ProcessorError::NotANode(json.clone()))?; - - if o.has_type(&id_from_iri(&Chronicle::Namespace)) { - self.apply_node_as_namespace(o)?; - } - if o.has_type(&id_from_iri(&Prov::Agent)) { - self.apply_node_as_agent(o)?; - } else if o.has_type(&id_from_iri(&Prov::Activity)) { - self.apply_node_as_activity(o)?; - } else if o.has_type(&id_from_iri(&Prov::Entity)) { - self.apply_node_as_entity(o)?; - } else if o.has_type(&id_from_iri(&Prov::Delegation)) { - self.apply_node_as_delegation(o)?; - } else if o.has_type(&id_from_iri(&Prov::Association)) { - self.apply_node_as_association(o)?; - } else if o.has_type(&id_from_iri(&Prov::Attribution)) { - self.apply_node_as_attribution(o)?; - } - } - Ok(()) - } else { - Err(ProcessorError::NotAnObject) - } - } - - /// Extract the types and find the first that is not prov::, as we currently only alow zero or one domain types - /// this should be sufficient - fn extract_attributes( - node: &Node, - ) -> Result { - let typ = node - .types() - .iter() - .filter_map(|x| x.as_iri()) - .find(|x| x.as_str().contains("domaintype")) - .map(|iri| Ok::<_, ProcessorError>(DomaintypeId::try_from(iri.as_iri())?)) - .transpose(); - - if let serde_json::Value::Object(map) = as_json(node) { - if let Some(serde_json::Value::Array(array)) = - map.get(Chronicle::Value.as_iri().as_str()) - { - if array.len() == 1 { - let o = array.get(0).unwrap(); - let serde_object = &o["@value"]; - - if let serde_json::Value::Object(object) = serde_object { - let attributes = object - .into_iter() - .map(|(typ, value)| { - ( - typ.clone(), - Attribute { - typ: typ.clone(), - value: value.clone().into(), - }, - ) - }) - .collect(); - - return Ok(Attributes { - typ: typ?, - attributes, - }); - } - } - } - } - - Err(ProcessorError::NotAnObject) - } - - fn apply_node_as_namespace( - &mut self, - ns: &Node, - ) -> Result<(), ProcessorError> { - let ns = ns.id().ok_or_else(|| ProcessorError::MissingId { - object: as_json(ns), - })?; - - self.namespace_context(&NamespaceId::try_from(Iri::from_str(ns.as_str())?)?); - - Ok(()) - } - - fn apply_node_as_delegation( - &mut self, - delegation: &Node, - ) -> Result<(), ProcessorError> { - let namespace_id = extract_namespace(delegation)?; - self.namespace_context(&namespace_id); - - let role = extract_scalar_prop(&Prov::HadRole, delegation) - .ok() - .and_then(|x| x.as_str().map(Role::from)); - - let responsible_id = extract_reference_ids(&Prov::ActedOnBehalfOf, delegation)? - .into_iter() - .next() - .ok_or_else(|| ProcessorError::MissingProperty { - object: as_json(delegation), - iri: Prov::ActedOnBehalfOf.as_iri().to_string(), - }) - .and_then(|x| Ok(AgentId::try_from(x.as_iri())?))?; - - let delegate_id = extract_reference_ids(&Prov::Delegate, delegation)? - .into_iter() - .next() - .ok_or_else(|| ProcessorError::MissingProperty { - object: as_json(delegation), - iri: Prov::Delegate.as_iri().to_string(), - }) - .and_then(|x| Ok(AgentId::try_from(x.as_iri())?))?; - - let activity_id = extract_reference_ids(&Prov::HadActivity, delegation)? - .into_iter() - .next() - .map(|x| ActivityId::try_from(x.as_iri())) - .transpose()?; - - self.qualified_delegation( - &namespace_id, - &responsible_id, - &delegate_id, - activity_id, - role, - ); - Ok(()) - } - - fn apply_node_as_association( - &mut self, - association: &Node, - ) -> Result<(), ProcessorError> { - let namespace_id = extract_namespace(association)?; - self.namespace_context(&namespace_id); - - let role = extract_scalar_prop(&Prov::HadRole, association) - .ok() - .and_then(|x| x.as_str().map(Role::from)); - - let agent_id = extract_reference_ids(&Prov::Responsible, association)? - .into_iter() - .next() - .ok_or_else(|| ProcessorError::MissingProperty { - object: as_json(association), - iri: Prov::Responsible.as_iri().to_string(), - }) - .and_then(|x| Ok(AgentId::try_from(x.as_iri())?))?; - - let activity_id = extract_reference_ids(&Prov::HadActivity, association)? - .into_iter() - .next() - .ok_or_else(|| ProcessorError::MissingProperty { - object: as_json(association), - iri: Prov::HadActivity.as_iri().to_string(), - }) - .and_then(|x| Ok(ActivityId::try_from(x.as_iri())?))?; - - self.qualified_association(&namespace_id, &activity_id, &agent_id, role); - - Ok(()) - } - - fn apply_node_as_attribution( - &mut self, - attribution: &Node, - ) -> Result<(), ProcessorError> { - let namespace_id = extract_namespace(attribution)?; - self.namespace_context(&namespace_id); - - let role = extract_scalar_prop(&Prov::HadRole, attribution) - .ok() - .and_then(|x| x.as_str().map(Role::from)); - - let agent_id = extract_reference_ids(&Prov::Responsible, attribution)? - .into_iter() - .next() - .ok_or_else(|| ProcessorError::MissingProperty { - object: as_json(attribution), - iri: Prov::Responsible.as_iri().to_string(), - }) - .and_then(|x| Ok(AgentId::try_from(x.as_iri())?))?; - - let entity_id = extract_reference_ids(&Prov::HadEntity, attribution)? - .into_iter() - .next() - .ok_or_else(|| ProcessorError::MissingProperty { - object: as_json(attribution), - iri: Prov::HadEntity.as_iri().to_string(), - }) - .and_then(|x| Ok(EntityId::try_from(x.as_iri())?))?; - - self.qualified_attribution(&namespace_id, &entity_id, &agent_id, role); - - Ok(()) - } - - fn apply_node_as_agent( - &mut self, - agent: &Node, - ) -> Result<(), ProcessorError> { - let id = AgentId::try_from(Iri::from_str( - agent - .id() - .ok_or_else(|| ProcessorError::MissingId { - object: as_json(agent), - })? - .as_str(), - )?)?; - - let namespaceid = extract_namespace(agent)?; - self.namespace_context(&namespaceid); - - let attributes = Self::extract_attributes(agent)?; - - let agent = Agent::exists(namespaceid, id).has_attributes(attributes); - - self.add_agent(agent); - - Ok(()) - } - - fn apply_node_as_activity( - &mut self, - activity: &Node, - ) -> Result<(), ProcessorError> { - let id = ActivityId::try_from(Iri::from_str( - activity - .id() - .ok_or_else(|| ProcessorError::MissingId { - object: as_json(activity), - })? - .as_str(), - )?)?; - - let namespaceid = extract_namespace(activity)?; - self.namespace_context(&namespaceid); - - let started = extract_scalar_prop(&Prov::StartedAtTime, activity) - .ok() - .and_then(|x| x.as_str().map(DateTime::parse_from_rfc3339)); - - let ended = extract_scalar_prop(&Prov::EndedAtTime, activity) - .ok() - .and_then(|x| x.as_str().map(DateTime::parse_from_rfc3339)); - - let used = extract_reference_ids(&Prov::Used, activity)? - .into_iter() - .map(|id| EntityId::try_from(id.as_iri())) - .collect::, _>>()?; - - let was_informed_by = extract_reference_ids(&Prov::WasInformedBy, activity)? - .into_iter() - .map(|id| ActivityId::try_from(id.as_iri())) - .collect::, _>>()?; - - let attributes = Self::extract_attributes(activity)?; - - let mut activity = Activity::exists(namespaceid.clone(), id).has_attributes(attributes); - - if let Some(started) = started { - activity.started = Some(DateTime::::from(started?).into()); - } - - if let Some(ended) = ended { - activity.ended = Some(DateTime::::from(ended?).into()); - } - - for entity in used { - self.used(namespaceid.clone(), &activity.id, &entity); - } - - for informing_activity in was_informed_by { - self.was_informed_by(namespaceid.clone(), &activity.id, &informing_activity); - } - - self.add_activity(activity); - - Ok(()) - } - - fn apply_node_as_entity( - &mut self, - entity: &Node, - ) -> Result<(), ProcessorError> { - let id = EntityId::try_from(Iri::from_str( - entity - .id() - .ok_or_else(|| ProcessorError::MissingId { - object: as_json(entity), - })? - .as_str(), - )?)?; - - let namespaceid = extract_namespace(entity)?; - self.namespace_context(&namespaceid); - - let generatedby = extract_reference_ids(&Prov::WasGeneratedBy, entity)? - .into_iter() - .map(|id| ActivityId::try_from(id.as_iri())) - .collect::, _>>()?; - - for derived in extract_reference_ids(&Prov::WasDerivedFrom, entity)? - .into_iter() - .map(|id| EntityId::try_from(id.as_iri())) - { - self.was_derived_from( - namespaceid.clone(), - DerivationType::None, - derived?, - id.clone(), - None, - ); - } - - for derived in extract_reference_ids(&Prov::WasQuotedFrom, entity)? - .into_iter() - .map(|id| EntityId::try_from(id.as_iri())) - { - self.was_derived_from( - namespaceid.clone(), - DerivationType::quotation(), - derived?, - id.clone(), - None, - ); - } - - for derived in extract_reference_ids(&Prov::WasRevisionOf, entity)? - .into_iter() - .map(|id| EntityId::try_from(id.as_iri())) - { - self.was_derived_from( - namespaceid.clone(), - DerivationType::revision(), - derived?, - id.clone(), - None, - ); - } - - for derived in extract_reference_ids(&Prov::HadPrimarySource, entity)? - .into_iter() - .map(|id| EntityId::try_from(id.as_iri())) - { - self.was_derived_from( - namespaceid.clone(), - DerivationType::primary_source(), - derived?, - id.clone(), - None, - ); - } - - for activity in generatedby { - self.was_generated_by(namespaceid.clone(), &id, &activity); - } - - let attributes = Self::extract_attributes(entity)?; - self.add_entity(Entity::exists(namespaceid, id).has_attributes(attributes)); - - Ok(()) - } -} - -trait Operation { - fn namespace(&self) -> NamespaceId; - fn agent(&self) -> AgentId; - fn delegate(&self) -> AgentId; - fn responsible(&self) -> AgentId; - fn optional_activity(&self) -> Option; - fn activity(&self) -> ActivityId; - fn optional_role(&self) -> Option; - fn key(&self) -> String; - fn start_time(&self) -> String; - fn locator(&self) -> Option; - fn end_time(&self) -> String; - fn entity(&self) -> EntityId; - fn used_entity(&self) -> EntityId; - fn derivation(&self) -> DerivationType; - fn domain(&self) -> Option; - fn attributes(&self) -> BTreeMap; - fn informing_activity(&self) -> ActivityId; -} - -impl Operation for Node { - fn namespace(&self) -> NamespaceId { - let mut uuid_objects = self.get(&id_from_iri(&ChronicleOperations::NamespaceUuid)); - let uuid = uuid_objects.next().unwrap().as_str().unwrap(); - let mut name_objects = self.get(&id_from_iri(&ChronicleOperations::NamespaceName)); - let external_id = name_objects.next().unwrap().as_str().unwrap(); - let uuid = uuid::Uuid::parse_str(uuid).unwrap(); - NamespaceId::from_external_id(external_id, uuid) - } - - fn agent(&self) -> AgentId { - let mut name_objects = self.get(&id_from_iri(&ChronicleOperations::AgentName)); - let external_id = name_objects.next().unwrap().as_str().unwrap(); - AgentId::from_external_id(external_id) - } - - fn delegate(&self) -> AgentId { - let mut name_objects = self.get(&id_from_iri(&ChronicleOperations::DelegateId)); - let external_id = name_objects.next().unwrap().as_str().unwrap(); - AgentId::from_external_id(external_id) - } - - fn optional_activity(&self) -> Option { - let mut name_objects = self.get(&id_from_iri(&ChronicleOperations::ActivityName)); - let object = match name_objects.next() { - Some(object) => object, - None => return None, - }; - Some(ActivityId::from_external_id(object.as_str().unwrap())) - } - - fn key(&self) -> String { - let mut objects = self.get(&id_from_iri(&ChronicleOperations::PublicKey)); - String::from(objects.next().unwrap().as_str().unwrap()) - } - - fn start_time(&self) -> String { - let mut objects = self.get(&id_from_iri(&ChronicleOperations::StartActivityTime)); - let time = objects.next().unwrap().as_str().unwrap(); - time.to_owned() - } - - fn end_time(&self) -> String { - let mut objects = self.get(&id_from_iri(&ChronicleOperations::EndActivityTime)); - let time = objects.next().unwrap().as_str().unwrap(); - time.to_owned() - } - - fn entity(&self) -> EntityId { - let mut name_objects = self.get(&id_from_iri(&ChronicleOperations::EntityName)); - let external_id = name_objects.next().unwrap().as_str().unwrap(); - EntityId::from_external_id(external_id) - } - - fn used_entity(&self) -> EntityId { - let mut name_objects = self.get(&id_from_iri(&ChronicleOperations::UsedEntityName)); - let external_id = name_objects.next().unwrap().as_str().unwrap(); - EntityId::from_external_id(external_id) - } - - fn derivation(&self) -> DerivationType { - let mut objects = self.get(&id_from_iri(&ChronicleOperations::DerivationType)); - let derivation = match objects.next() { - Some(object) => object.as_str().unwrap(), - None => return DerivationType::None, - }; - - match derivation { - "Revision" => DerivationType::Revision, - "Quotation" => DerivationType::Quotation, - "PrimarySource" => DerivationType::PrimarySource, - _ => unreachable!(), - } - } - - fn domain(&self) -> Option { - let mut objects = self.get(&id_from_iri(&ChronicleOperations::DomaintypeId)); - let d = match objects.next() { - Some(object) => object.as_str().unwrap(), - None => return None, - }; - Some(DomaintypeId::from_external_id(d)) - } - - fn attributes(&self) -> BTreeMap { - self.get(&id_from_iri(&ChronicleOperations::Attributes)) - .map(|o| { - let serde_object = - if let Some(json_ld::object::Value::Json(Meta(json, _))) = o.as_value() { - json.clone().into() - } else { - serde_json::from_str(&as_json(o.as_node().unwrap())["@value"].to_string()) - .unwrap() - }; - - if let serde_json::Value::Object(object) = serde_object { - Ok(object - .into_iter() - .map(|(typ, value)| Attribute { - typ, - value: value.into(), - }) - .collect::>()) - } else { - Err(ProcessorError::NotAnObject {}) - } - }) - .collect::, _>>() - .unwrap() - .into_iter() - .flatten() - .map(|attr| (attr.typ.clone(), attr)) - .collect() - } - - fn responsible(&self) -> AgentId { - let mut name_objects = self.get(&id_from_iri(&ChronicleOperations::ResponsibleId)); - let external_id = name_objects.next().unwrap().as_str().unwrap(); - AgentId::from_external_id(external_id) - } - - fn optional_role(&self) -> Option { - let mut name_objects = self.get(&id_from_iri(&ChronicleOperations::Role)); - let object = match name_objects.next() { - Some(object) => object, - None => return None, - }; - Some(Role::from(object.as_str().unwrap())) - } - - fn activity(&self) -> ActivityId { - let mut name_objects = self.get(&id_from_iri(&ChronicleOperations::ActivityName)); - let external_id = name_objects.next().unwrap().as_str().unwrap(); - ActivityId::from_external_id(external_id) - } - - fn locator(&self) -> Option { - let mut objects = self.get(&id_from_iri(&ChronicleOperations::Locator)); - - let locator = match objects.next() { - Some(object) => object, - None => return None, - }; - - Some(locator.as_str().unwrap().to_owned()) - } - - fn informing_activity(&self) -> ActivityId { - let mut name_objects = self.get(&id_from_iri(&ChronicleOperations::InformingActivityName)); - let external_id = name_objects.next().unwrap().as_str().unwrap(); - ActivityId::from_external_id(external_id) - } -} - -impl ChronicleOperation { - pub async fn from_json(json: &Value) -> Result { - use json_ld::Expand; - - let mut output = json_ld::syntax::Value::from_serde_json(json.clone(), |_| ()) - .expand(&mut ContextLoader) - .await - .map_err(|e| ProcessorError::Expansion { - inner: format!("{e:?}"), - })?; - - output.canonicalize(); - if let Some(object) = output.into_value().into_objects().into_iter().next() { - let o = object - .value() - .inner() - .as_node() - .ok_or(ProcessorError::NotANode(json.clone()))?; - if o.has_type(&id_from_iri(&ChronicleOperations::CreateNamespace)) { - let namespace = o.namespace(); - let external_id = namespace.external_id_part().to_owned(); - let uuid = namespace.uuid_part().to_owned(); - Ok(ChronicleOperation::CreateNamespace(CreateNamespace { - id: namespace, - external_id, - uuid: uuid.into(), - })) - } else if o.has_type(&id_from_iri(&ChronicleOperations::AgentExists)) { - let namespace = o.namespace(); - let agent = o.agent(); - let external_id = agent.external_id_part(); - Ok(ChronicleOperation::AgentExists(AgentExists { - namespace, - external_id: external_id.into(), - })) - } else if o.has_type(&id_from_iri(&ChronicleOperations::AgentActsOnBehalfOf)) { - let namespace = o.namespace(); - let delegate_id = o.delegate(); - let responsible_id = o.responsible(); - let activity_id = o.optional_activity(); - - Ok(ChronicleOperation::AgentActsOnBehalfOf( - ActsOnBehalfOf::new( - &namespace, - &responsible_id, - &delegate_id, - activity_id.as_ref(), - o.optional_role(), - ), - )) - } else if o.has_type(&id_from_iri(&ChronicleOperations::ActivityExists)) { - let namespace = o.namespace(); - let activity_id = o.optional_activity().unwrap(); - let external_id = activity_id.external_id_part().to_owned(); - Ok(ChronicleOperation::ActivityExists(ActivityExists { - namespace, - external_id, - })) - } else if o.has_type(&id_from_iri(&ChronicleOperations::StartActivity)) { - let namespace = o.namespace(); - let id = o.optional_activity().unwrap(); - let time: DateTime = o.start_time().parse().unwrap(); - Ok(ChronicleOperation::StartActivity(StartActivity { - namespace, - id, - time: time.into(), - })) - } else if o.has_type(&id_from_iri(&ChronicleOperations::EndActivity)) { - let namespace = o.namespace(); - let id = o.optional_activity().unwrap(); - let time: DateTime = o.end_time().parse().unwrap(); - Ok(ChronicleOperation::EndActivity(EndActivity { - namespace, - id, - time: time.into(), - })) - } else if o.has_type(&id_from_iri(&ChronicleOperations::ActivityUses)) { - let namespace = o.namespace(); - let id = o.entity(); - let activity = o.optional_activity().unwrap(); - Ok(ChronicleOperation::ActivityUses(ActivityUses { - namespace, - id, - activity, - })) - } else if o.has_type(&id_from_iri(&ChronicleOperations::EntityExists)) { - let namespace = o.namespace(); - let entity = o.entity(); - let id = entity.external_id_part().into(); - Ok(ChronicleOperation::EntityExists(EntityExists { - namespace, - external_id: id, - })) - } else if o.has_type(&id_from_iri(&ChronicleOperations::WasGeneratedBy)) { - let namespace = o.namespace(); - let id = o.entity(); - let activity = o.optional_activity().unwrap(); - Ok(ChronicleOperation::WasGeneratedBy(WasGeneratedBy { - namespace, - id, - activity, - })) - } else if o.has_type(&id_from_iri(&ChronicleOperations::EntityDerive)) { - let namespace = o.namespace(); - let id = o.entity(); - let used_id = o.used_entity(); - let activity_id = o.optional_activity(); - let typ = o.derivation(); - Ok(ChronicleOperation::EntityDerive(EntityDerive { - namespace, - id, - used_id, - activity_id, - typ, - })) - } else if o.has_type(&id_from_iri(&ChronicleOperations::SetAttributes)) { - let namespace = o.namespace(); - let domain = o.domain(); - - let attrs = o.attributes(); - - let attributes = Attributes { - typ: domain, - attributes: attrs, - }; - let actor: SetAttributes = { - if o.has_key(&Term::Id(id_from_iri(&ChronicleOperations::EntityName))) { - let id = o.entity(); - SetAttributes::Entity { - namespace, - id, - attributes, - } - } else if o.has_key(&Term::Id(id_from_iri(&ChronicleOperations::AgentName))) { - let id = o.agent(); - SetAttributes::Agent { - namespace, - id, - attributes, - } - } else { - let id = o.optional_activity().unwrap(); - SetAttributes::Activity { - namespace, - id, - attributes, - } - } - }; - - Ok(ChronicleOperation::SetAttributes(actor)) - } else if o.has_type(&id_from_iri(&ChronicleOperations::WasAssociatedWith)) { - Ok(ChronicleOperation::WasAssociatedWith( - WasAssociatedWith::new( - &o.namespace(), - &o.activity(), - &o.agent(), - o.optional_role(), - ), - )) - } else if o.has_type(&id_from_iri(&ChronicleOperations::WasAttributedTo)) { - Ok(ChronicleOperation::WasAttributedTo(WasAttributedTo::new( - &o.namespace(), - &o.entity(), - &o.agent(), - o.optional_role(), - ))) - } else if o.has_type(&id_from_iri(&ChronicleOperations::WasInformedBy)) { - let namespace = o.namespace(); - let activity = o.activity(); - let informing_activity = o.informing_activity(); - Ok(ChronicleOperation::WasInformedBy(WasInformedBy { - namespace, - activity, - informing_activity, - })) - } else { - error!("Unknown operation: {:?}", o.type_entry()); - unreachable!() - } - } else { - Err(ProcessorError::NotANode(json.clone())) - } - } -} diff --git a/crates/common/src/prov/model/json_ld/from_json_ld.rs b/crates/common/src/prov/model/json_ld/from_json_ld.rs new file mode 100644 index 000000000..b45f2fb76 --- /dev/null +++ b/crates/common/src/prov/model/json_ld/from_json_ld.rs @@ -0,0 +1,812 @@ +use chrono::{DateTime, Utc}; + +use futures::{future::BoxFuture, FutureExt}; +use iref::{Iri, IriBuf, IriRef}; +use iri_string::types::{IriAbsoluteString, UriAbsoluteStr, UriAbsoluteString}; +use json_ld::{ + syntax::IntoJsonWithContextMeta, Indexed, Loader, Node, Profile, RemoteDocument, Term, +}; +use locspan::Meta; +use mime::Mime; +use rdf_types::{vocabulary::no_vocabulary_mut, BlankIdBuf, IriVocabularyMut}; +use serde_json::{json, Value}; +use std::collections::BTreeMap; +use tracing::{error, instrument, trace}; + +#[cfg(not(feature = "std"))] +use parity_scale_codec::{alloc::string::String, alloc::vec::Vec, prelude::*}; + +use crate::{ + attributes::{Attribute, Attributes}, + prov::{ + operations::{ + ActivityExists, ActivityUses, ActsOnBehalfOf, AgentExists, ChronicleOperation, + CreateNamespace, DerivationType, EndActivity, EntityDerive, EntityExists, + SetAttributes, StartActivity, WasAssociatedWith, WasAttributedTo, WasGeneratedBy, + WasInformedBy, + }, + vocab::{Chronicle, ChronicleOperations, Prov}, + ActivityId, AgentId, DomaintypeId, EntityId, ExternalIdPart, NamespaceId, Role, UuidPart, + }, +}; + +use crate::prov::{Activity, Agent, Entity, ProcessorError, ProvModel}; + +pub struct ContextLoader; + +impl Loader for ContextLoader { + type Error = (); + type Output = json_ld::syntax::Value; + + // This is only used to load the context, so we can just return it directly + fn load_with<'b>( + &'b mut self, + vocabulary: &'b mut (impl Sync + Send + IriVocabularyMut), + url: IriBuf, + ) -> BoxFuture, Self::Error>> + where + IriBuf: 'b, + { + use hashbrown::HashSet; + use std::str::FromStr; + let mut profiles = HashSet::new(); + profiles.insert(Profile::new(url.as_iri(), vocabulary)); + trace!("Loading context from {}", url); + async move { + let json = json!({ + "@context": crate::context::PROV.clone() + }); + let value = json_ld::syntax::Value::from_serde_json(json, |_| ()); + Ok(json_ld::RemoteDocument::new_full( + Some(url), + Some(Mime::from_str("application/json").unwrap()), + None, + profiles, + value, + )) + } + .boxed() + } +} + +fn as_json(node: &Node) -> serde_json::Value { + node.clone().into_json_meta_with((), no_vocabulary_mut()).into_value().into() +} + +// Convert with coercion from our vocab iris, this is safe as sourced from constants +fn id_from_iri_string>(iri: I) -> json_ld::Id { + json_ld::Id::Valid(json_ld::ValidId::Iri(IriBuf::try_from(iri.into().to_string()).unwrap())) +} + +fn extract_reference_ids>( + iri: I, + node: &Node, +) -> Result, ProcessorError> { + let ids: Result, _> = node + .get(&id_from_iri_string(iri)) + .map(|o| o.id().ok_or_else(|| ProcessorError::MissingId { object: as_json(node) })) + .map(|id| { + id.and_then(|id| { + id.as_iri().ok_or_else(|| ProcessorError::MissingId { object: as_json(node) }) + }) + }) + .map(|id| id.map(|id| id.to_owned())) + .collect(); + + ids +} + +fn extract_scalar_prop<'a, I: Into + Clone>( + iri: I, + node: &'a Node, +) -> Result<&'a Indexed, ()>, ProcessorError> { + if let Some(object) = node.get_any(&id_from_iri_string(iri.clone())) { + Ok(object) + } else { + Err(ProcessorError::MissingProperty { iri: iri.into().to_string(), object: as_json(node) }) + } +} + +fn extract_namespace(agent: &Node) -> Result { + Ok(NamespaceId::try_from( + extract_scalar_prop(Chronicle::HasNamespace, agent)? + .id() + .ok_or(ProcessorError::MissingId { object: as_json(agent) })? + .to_string(), + )?) +} + +impl ProvModel { + pub async fn apply_json_ld_str(&mut self, buf: &str) -> Result<(), ProcessorError> { + self.apply_json_ld(serde_json::from_str(buf)?).await?; + + Ok(()) + } + + pub async fn apply_json_ld_bytes(&mut self, buf: &[u8]) -> Result<(), ProcessorError> { + self.apply_json_ld(serde_json::from_slice(buf)?).await?; + + Ok(()) + } + + /// Take a Json-Ld input document, assuming it is in compact form, expand it and apply the state + /// to the prov model Replace @context with our resource context + /// We rely on reified @types, so subclassing must also include supertypes + #[instrument(level = "trace", skip(self, json))] + pub async fn apply_json_ld(&mut self, json: serde_json::Value) -> Result<(), ProcessorError> { + if let serde_json::Value::Object(mut map) = json { + map.insert( + "@context".to_string(), + serde_json::Value::String("https://btp.works/chr/1.0/c.jsonld".to_string()), + ); + let json = serde_json::Value::Object(map); + + trace!(to_apply_compact=%serde_json::to_string_pretty(&json)?); + + use json_ld::Expand; + let output = json_ld::syntax::Value::from_serde_json(json.clone(), |_| ()) + .expand(&mut ContextLoader) + .await + .map_err(|e| ProcessorError::Expansion { inner: format!("{e:?}") })?; + + for o in output.into_value().into_objects() { + let o = + o.value().inner().as_node().ok_or(ProcessorError::NotANode(json.clone()))?; + + if o.has_type(&id_from_iri_string(Chronicle::Namespace)) { + self.apply_node_as_namespace(o)?; + } + if o.has_type(&id_from_iri_string(Prov::Agent)) { + self.apply_node_as_agent(o)?; + } else if o.has_type(&id_from_iri_string(Prov::Activity)) { + self.apply_node_as_activity(o)?; + } else if o.has_type(&id_from_iri_string(Prov::Entity)) { + self.apply_node_as_entity(o)?; + } else if o.has_type(&id_from_iri_string(Prov::Delegation)) { + self.apply_node_as_delegation(o)?; + } else if o.has_type(&id_from_iri_string(Prov::Association)) { + self.apply_node_as_association(o)?; + } else if o.has_type(&id_from_iri_string(Prov::Attribution)) { + self.apply_node_as_attribution(o)?; + } + } + Ok(()) + } else { + Err(ProcessorError::NotAnObject) + } + } + + /// Extract the types and find the first that is not prov::, as we currently only alow zero or + /// one domain types this should be sufficient + fn extract_attributes( + node: &Node, + ) -> Result { + let typ = node + .types() + .iter() + .filter_map(|x| x.as_iri()) + .find(|x| x.as_str().contains("domaintype")) + .map(|iri| Ok::<_, ProcessorError>(DomaintypeId::try_from(iri.to_string())?)) + .transpose(); + + if let serde_json::Value::Object(map) = as_json(node) { + if let Some(serde_json::Value::Array(array)) = map.get(Chronicle::Value.as_str()) { + if array.len() == 1 { + let o = array.get(0).unwrap(); + let serde_object = &o["@value"]; + + if let serde_json::Value::Object(object) = serde_object { + let attributes = object + .into_iter() + .map(|(typ, value)| { + ( + typ.clone(), + Attribute { typ: typ.clone(), value: value.clone().into() }, + ) + }) + .collect(); + + return Ok(Attributes { typ: typ?, attributes }); + } + } + } + } + + Err(ProcessorError::NotAnObject) + } + + fn apply_node_as_namespace( + &mut self, + ns: &Node, + ) -> Result<(), ProcessorError> { + let ns = ns.id().ok_or_else(|| ProcessorError::MissingId { object: as_json(ns) })?; + + self.namespace_context(&NamespaceId::try_from(ns.to_string())?); + + Ok(()) + } + + fn apply_node_as_delegation( + &mut self, + delegation: &Node, + ) -> Result<(), ProcessorError> { + let namespace_id = extract_namespace(delegation)?; + self.namespace_context(&namespace_id); + + let role = extract_scalar_prop(Prov::HadRole, delegation) + .ok() + .and_then(|x| x.as_str().map(Role::from)); + + let responsible_id = extract_reference_ids(Prov::ActedOnBehalfOf, delegation)? + .into_iter() + .next() + .ok_or_else(|| ProcessorError::MissingProperty { + object: as_json(delegation), + iri: Prov::ActedOnBehalfOf.to_string(), + }) + .and_then(|x| Ok(AgentId::try_from(x.to_string())?))?; + + let delegate_id = extract_reference_ids(Prov::Delegate, delegation)? + .into_iter() + .next() + .ok_or_else(|| ProcessorError::MissingProperty { + object: as_json(delegation), + iri: Prov::Delegate.to_string(), + }) + .and_then(|x| Ok(AgentId::try_from(x.to_string())?))?; + + let activity_id = extract_reference_ids(Prov::HadActivity, delegation)? + .into_iter() + .next() + .map(|x| ActivityId::try_from(x.to_string())) + .transpose()?; + + self.qualified_delegation(&namespace_id, &responsible_id, &delegate_id, activity_id, role); + Ok(()) + } + + fn apply_node_as_association( + &mut self, + association: &Node, + ) -> Result<(), ProcessorError> { + let namespace_id = extract_namespace(association)?; + self.namespace_context(&namespace_id); + + let role = extract_scalar_prop(Prov::HadRole, association) + .ok() + .and_then(|x| x.as_str().map(Role::from)); + + let agent_id = extract_reference_ids(Prov::Responsible, association)? + .into_iter() + .next() + .ok_or_else(|| ProcessorError::MissingProperty { + object: as_json(association), + iri: Prov::Responsible.to_string(), + }) + .and_then(|x| Ok(AgentId::try_from(x.to_string())?))?; + + let activity_id = extract_reference_ids(Prov::HadActivity, association)? + .into_iter() + .next() + .ok_or_else(|| ProcessorError::MissingProperty { + object: as_json(association), + iri: Prov::HadActivity.to_string(), + }) + .and_then(|x| Ok(ActivityId::try_from(x.to_string())?))?; + + self.qualified_association(&namespace_id, &activity_id, &agent_id, role); + + Ok(()) + } + + fn apply_node_as_attribution( + &mut self, + attribution: &Node, + ) -> Result<(), ProcessorError> { + let namespace_id = extract_namespace(attribution)?; + self.namespace_context(&namespace_id); + + let role = extract_scalar_prop(Prov::HadRole, attribution) + .ok() + .and_then(|x| x.as_str().map(Role::from)); + + let agent_id = extract_reference_ids(Prov::Responsible, attribution)? + .into_iter() + .next() + .ok_or_else(|| ProcessorError::MissingProperty { + object: as_json(attribution), + iri: Prov::Responsible.to_string(), + }) + .and_then(|x| Ok(AgentId::try_from(x.to_string())?))?; + + let entity_id = extract_reference_ids(Prov::HadEntity, attribution)? + .into_iter() + .next() + .ok_or_else(|| ProcessorError::MissingProperty { + object: as_json(attribution), + iri: Prov::HadEntity.to_string(), + }) + .and_then(|x| Ok(EntityId::try_from(x.to_string())?))?; + + self.qualified_attribution(&namespace_id, &entity_id, &agent_id, role); + + Ok(()) + } + + fn apply_node_as_agent( + &mut self, + agent: &Node, + ) -> Result<(), ProcessorError> { + let id = AgentId::try_from( + agent + .id() + .ok_or_else(|| ProcessorError::MissingId { object: as_json(agent) })? + .to_string(), + )?; + + let namespaceid = extract_namespace(agent)?; + self.namespace_context(&namespaceid); + + let attributes = Self::extract_attributes(agent)?; + + let agent = Agent::exists(namespaceid, id).has_attributes(attributes); + + self.add_agent(agent); + + Ok(()) + } + + fn apply_node_as_activity( + &mut self, + activity: &Node, + ) -> Result<(), ProcessorError> { + let id = ActivityId::try_from( + activity + .id() + .ok_or_else(|| ProcessorError::MissingId { object: as_json(activity) })? + .to_string(), + )?; + + let namespaceid = extract_namespace(activity)?; + self.namespace_context(&namespaceid); + + let started = extract_scalar_prop(Prov::StartedAtTime, activity) + .ok() + .and_then(|x| x.as_str().map(DateTime::parse_from_rfc3339)); + + let ended = extract_scalar_prop(Prov::EndedAtTime, activity) + .ok() + .and_then(|x| x.as_str().map(DateTime::parse_from_rfc3339)); + + let used = extract_reference_ids(Prov::Used, activity)? + .into_iter() + .map(|id| EntityId::try_from(id.to_string())) + .collect::, _>>()?; + + let was_informed_by = extract_reference_ids(Prov::WasInformedBy, activity)? + .into_iter() + .map(|id| ActivityId::try_from(id.to_string())) + .collect::, _>>()?; + + let attributes = Self::extract_attributes(activity)?; + + let mut activity = Activity::exists(namespaceid.clone(), id).has_attributes(attributes); + + if let Some(started) = started { + activity.started = Some(DateTime::::from(started?).into()); + } + + if let Some(ended) = ended { + activity.ended = Some(DateTime::::from(ended?).into()); + } + + for entity in used { + self.used(namespaceid.clone(), &activity.id, &entity); + } + + for informing_activity in was_informed_by { + self.was_informed_by(namespaceid.clone(), &activity.id, &informing_activity); + } + + self.add_activity(activity); + + Ok(()) + } + + fn apply_node_as_entity( + &mut self, + entity: &Node, + ) -> Result<(), ProcessorError> { + let id = EntityId::try_from( + entity + .id() + .ok_or_else(|| ProcessorError::MissingId { object: as_json(entity) })? + .to_string(), + )?; + + let namespaceid = extract_namespace(entity)?; + self.namespace_context(&namespaceid); + + let generatedby = extract_reference_ids(Prov::WasGeneratedBy, entity)? + .into_iter() + .map(|id| ActivityId::try_from(id.to_string())) + .collect::, _>>()?; + + for derived in extract_reference_ids(Prov::WasDerivedFrom, entity)? + .into_iter() + .map(|id| EntityId::try_from(id.to_string())) + { + self.was_derived_from( + namespaceid.clone(), + DerivationType::None, + derived?, + id.clone(), + None, + ); + } + + for derived in extract_reference_ids(Prov::WasQuotedFrom, entity)? + .into_iter() + .map(|id| EntityId::try_from(id.to_string())) + { + self.was_derived_from( + namespaceid.clone(), + DerivationType::quotation(), + derived?, + id.clone(), + None, + ); + } + + for derived in extract_reference_ids(Prov::WasRevisionOf, entity)? + .into_iter() + .map(|id| EntityId::try_from(id.to_string())) + { + self.was_derived_from( + namespaceid.clone(), + DerivationType::revision(), + derived?, + id.clone(), + None, + ); + } + + for derived in extract_reference_ids(Prov::HadPrimarySource, entity)? + .into_iter() + .map(|id| EntityId::try_from(id.to_string())) + { + self.was_derived_from( + namespaceid.clone(), + DerivationType::primary_source(), + derived?, + id.clone(), + None, + ); + } + + for activity in generatedby { + self.was_generated_by(namespaceid.clone(), &id, &activity); + } + + let attributes = Self::extract_attributes(entity)?; + self.add_entity(Entity::exists(namespaceid, id).has_attributes(attributes)); + + Ok(()) + } +} + +trait Operation { + fn namespace(&self) -> NamespaceId; + fn agent(&self) -> AgentId; + fn delegate(&self) -> AgentId; + fn responsible(&self) -> AgentId; + fn optional_activity(&self) -> Option; + fn activity(&self) -> ActivityId; + fn optional_role(&self) -> Option; + fn start_time(&self) -> String; + fn locator(&self) -> Option; + fn end_time(&self) -> String; + fn entity(&self) -> EntityId; + fn used_entity(&self) -> EntityId; + fn derivation(&self) -> DerivationType; + fn domain(&self) -> Option; + fn attributes(&self) -> BTreeMap; + fn informing_activity(&self) -> ActivityId; +} + +impl Operation for Node { + fn namespace(&self) -> NamespaceId { + let mut uuid_objects = self.get(&id_from_iri_string(ChronicleOperations::NamespaceUuid)); + let uuid = uuid_objects.next().unwrap().as_str().unwrap(); + let mut name_objects = self.get(&id_from_iri_string(ChronicleOperations::NamespaceName)); + let external_id = name_objects.next().unwrap().as_str().unwrap(); + let uuid = uuid::Uuid::parse_str(uuid).unwrap(); + NamespaceId::from_external_id(external_id, uuid) + } + + fn agent(&self) -> AgentId { + let mut name_objects = self.get(&id_from_iri_string(ChronicleOperations::AgentName)); + let external_id = name_objects.next().unwrap().as_str().unwrap(); + AgentId::from_external_id(external_id) + } + + fn delegate(&self) -> AgentId { + let mut name_objects = self.get(&id_from_iri_string(ChronicleOperations::DelegateId)); + let external_id = name_objects.next().unwrap().as_str().unwrap(); + AgentId::from_external_id(external_id) + } + + fn optional_activity(&self) -> Option { + let mut name_objects = self.get(&id_from_iri_string(ChronicleOperations::ActivityName)); + let object = match name_objects.next() { + Some(object) => object, + None => return None, + }; + Some(ActivityId::from_external_id(object.as_str().unwrap())) + } + + fn start_time(&self) -> String { + let mut objects = self.get(&id_from_iri_string(ChronicleOperations::StartActivityTime)); + let time = objects.next().unwrap().as_str().unwrap(); + time.to_owned() + } + + fn end_time(&self) -> String { + let mut objects = self.get(&id_from_iri_string(ChronicleOperations::EndActivityTime)); + let time = objects.next().unwrap().as_str().unwrap(); + time.to_owned() + } + + fn entity(&self) -> EntityId { + let mut name_objects = self.get(&id_from_iri_string(ChronicleOperations::EntityName)); + let external_id = name_objects.next().unwrap().as_str().unwrap(); + EntityId::from_external_id(external_id) + } + + fn used_entity(&self) -> EntityId { + let mut name_objects = self.get(&id_from_iri_string(ChronicleOperations::UsedEntityName)); + let external_id = name_objects.next().unwrap().as_str().unwrap(); + EntityId::from_external_id(external_id) + } + + fn derivation(&self) -> DerivationType { + let mut objects = self.get(&id_from_iri_string(ChronicleOperations::DerivationType)); + let derivation = match objects.next() { + Some(object) => object.as_str().unwrap(), + None => return DerivationType::None, + }; + + match derivation { + "Revision" => DerivationType::Revision, + "Quotation" => DerivationType::Quotation, + "PrimarySource" => DerivationType::PrimarySource, + _ => unreachable!(), + } + } + + fn domain(&self) -> Option { + let mut objects = self.get(&id_from_iri_string(ChronicleOperations::DomaintypeId)); + let d = match objects.next() { + Some(object) => object.as_str().unwrap(), + None => return None, + }; + Some(DomaintypeId::from_external_id(d)) + } + + fn attributes(&self) -> BTreeMap { + self.get(&id_from_iri_string(ChronicleOperations::Attributes)) + .map(|o| { + let serde_object = + if let Some(json_ld::object::Value::Json(Meta(json, _))) = o.as_value() { + json.clone().into() + } else { + serde_json::from_str(&as_json(o.as_node().unwrap())["@value"].to_string()) + .unwrap() + }; + + if let serde_json::Value::Object(object) = serde_object { + Ok(object + .into_iter() + .map(|(typ, value)| Attribute { typ, value: value.into() }) + .collect::>()) + } else { + Err(ProcessorError::NotAnObject {}) + } + }) + .collect::, _>>() + .unwrap() + .into_iter() + .flatten() + .map(|attr| (attr.typ.clone(), attr)) + .collect() + } + + fn responsible(&self) -> AgentId { + let mut name_objects = self.get(&id_from_iri_string(ChronicleOperations::ResponsibleId)); + let external_id = name_objects.next().unwrap().as_str().unwrap(); + AgentId::from_external_id(external_id) + } + + fn optional_role(&self) -> Option { + let mut name_objects = self.get(&id_from_iri_string(ChronicleOperations::Role)); + let object = match name_objects.next() { + Some(object) => object, + None => return None, + }; + Some(Role::from(object.as_str().unwrap())) + } + + fn activity(&self) -> ActivityId { + let mut name_objects = self.get(&id_from_iri_string(ChronicleOperations::ActivityName)); + let external_id = name_objects.next().unwrap().as_str().unwrap(); + ActivityId::from_external_id(external_id) + } + + fn locator(&self) -> Option { + let mut objects = self.get(&id_from_iri_string(ChronicleOperations::Locator)); + + let locator = match objects.next() { + Some(object) => object, + None => return None, + }; + + Some(locator.as_str().unwrap().to_owned()) + } + + fn informing_activity(&self) -> ActivityId { + let mut name_objects = + self.get(&id_from_iri_string(ChronicleOperations::InformingActivityName)); + let external_id = name_objects.next().unwrap().as_str().unwrap(); + ActivityId::from_external_id(external_id) + } +} + +impl ChronicleOperation { + pub async fn from_json(json: &Value) -> Result { + use json_ld::Expand; + + let mut output = json_ld::syntax::Value::from_serde_json(json.clone(), |_| ()) + .expand(&mut ContextLoader) + .await + .map_err(|e| ProcessorError::Expansion { inner: format!("{e:?}") })?; + + output.canonicalize(); + if let Some(object) = output.into_value().into_objects().into_iter().next() { + let o = + object.value().inner().as_node().ok_or(ProcessorError::NotANode(json.clone()))?; + if o.has_type(&id_from_iri_string(ChronicleOperations::CreateNamespace)) { + let namespace = o.namespace(); + let external_id = namespace.external_id_part().to_owned(); + let uuid = namespace.uuid_part().to_owned(); + Ok(ChronicleOperation::CreateNamespace(CreateNamespace { + id: namespace, + external_id, + uuid: uuid.into(), + })) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::AgentExists)) { + let namespace = o.namespace(); + let agent = o.agent(); + let external_id = agent.external_id_part(); + Ok(ChronicleOperation::AgentExists(AgentExists { + namespace, + external_id: external_id.into(), + })) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::AgentActsOnBehalfOf)) { + let namespace = o.namespace(); + let delegate_id = o.delegate(); + let responsible_id = o.responsible(); + let activity_id = o.optional_activity(); + + Ok(ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf::new( + &namespace, + &responsible_id, + &delegate_id, + activity_id.as_ref(), + o.optional_role(), + ))) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::ActivityExists)) { + let namespace = o.namespace(); + let activity_id = o.optional_activity().unwrap(); + let external_id = activity_id.external_id_part().to_owned(); + Ok(ChronicleOperation::ActivityExists(ActivityExists { namespace, external_id })) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::StartActivity)) { + let namespace = o.namespace(); + let id = o.optional_activity().unwrap(); + let time: DateTime = o.start_time().parse().unwrap(); + Ok(ChronicleOperation::StartActivity(StartActivity { + namespace, + id, + time: time.into(), + })) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::EndActivity)) { + let namespace = o.namespace(); + let id = o.optional_activity().unwrap(); + let time: DateTime = o.end_time().parse().unwrap(); + Ok(ChronicleOperation::EndActivity(EndActivity { + namespace, + id, + time: time.into(), + })) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::ActivityUses)) { + let namespace = o.namespace(); + let id = o.entity(); + let activity = o.optional_activity().unwrap(); + Ok(ChronicleOperation::ActivityUses(ActivityUses { namespace, id, activity })) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::EntityExists)) { + let namespace = o.namespace(); + let entity = o.entity(); + let id = entity.external_id_part().into(); + Ok(ChronicleOperation::EntityExists(EntityExists { namespace, external_id: id })) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::WasGeneratedBy)) { + let namespace = o.namespace(); + let id = o.entity(); + let activity = o.optional_activity().unwrap(); + Ok(ChronicleOperation::WasGeneratedBy(WasGeneratedBy { namespace, id, activity })) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::EntityDerive)) { + let namespace = o.namespace(); + let id = o.entity(); + let used_id = o.used_entity(); + let activity_id = o.optional_activity(); + let typ = o.derivation(); + Ok(ChronicleOperation::EntityDerive(EntityDerive { + namespace, + id, + used_id, + activity_id, + typ, + })) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::SetAttributes)) { + let namespace = o.namespace(); + let domain = o.domain(); + + let attrs = o.attributes(); + + let attributes = Attributes { typ: domain, attributes: attrs }; + let actor: SetAttributes = { + if o.has_key(&Term::Id(id_from_iri_string(ChronicleOperations::EntityName))) { + let id = o.entity(); + SetAttributes::Entity { namespace, id, attributes } + } else if o + .has_key(&Term::Id(id_from_iri_string(ChronicleOperations::AgentName))) + { + let id = o.agent(); + SetAttributes::Agent { namespace, id, attributes } + } else { + let id = o.optional_activity().unwrap(); + SetAttributes::Activity { namespace, id, attributes } + } + }; + + Ok(ChronicleOperation::SetAttributes(actor)) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::WasAssociatedWith)) { + Ok(ChronicleOperation::WasAssociatedWith(WasAssociatedWith::new( + &o.namespace(), + &o.activity(), + &o.agent(), + o.optional_role(), + ))) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::WasAttributedTo)) { + Ok(ChronicleOperation::WasAttributedTo(WasAttributedTo::new( + &o.namespace(), + &o.entity(), + &o.agent(), + o.optional_role(), + ))) + } else if o.has_type(&id_from_iri_string(ChronicleOperations::WasInformedBy)) { + let namespace = o.namespace(); + let activity = o.activity(); + let informing_activity = o.informing_activity(); + Ok(ChronicleOperation::WasInformedBy(WasInformedBy { + namespace, + activity, + informing_activity, + })) + } else { + error!("Unknown operation: {:?}", o.type_entry()); + unreachable!() + } + } else { + Err(ProcessorError::NotANode(json.clone())) + } + } +} diff --git a/crates/common/src/prov/model/json_ld/mod.rs b/crates/common/src/prov/model/json_ld/mod.rs new file mode 100644 index 000000000..8fc7d4c9b --- /dev/null +++ b/crates/common/src/prov/model/json_ld/mod.rs @@ -0,0 +1,187 @@ +mod from_json_ld; +mod to_json_ld; + +pub use from_json_ld::*; +pub use to_json_ld::*; + +use iref::IriBuf; +use json_ld::NoLoader; +use lazy_static::lazy_static; +use locspan::Meta; +use rdf_types::{vocabulary::no_vocabulary_mut, BlankIdBuf}; +use serde_json::Value; + +#[cfg(feature = "std")] +use thiserror::Error; +#[cfg(not(feature = "std"))] +use thiserror_no_std::Error; + +#[derive(Error, Debug)] +pub enum CompactionError { + #[error("JSON-LD: {inner}")] + JsonLd { inner: String }, + #[error("Tokio")] + Join, + #[error("Serde conversion: {source}")] + Serde { + #[from] + source: serde_json::Error, + }, + #[error("Expanded document invalid: {message}")] + InvalidExpanded { message: String }, + #[error("Compacted document not a JSON object: {document}")] + NoObject { document: Value }, +} +pub struct ExpandedJson(pub serde_json::Value); + +fn construct_context_definition( + json: &serde_json::Value, + metadata: M, +) -> json_ld::syntax::context::Definition +where + M: Clone + core::fmt::Debug, +{ + use json_ld::syntax::{ + context::{ + definition::{Bindings, Version}, + Definition, TermDefinition, + }, + Entry, Nullable, TryFromJson, + }; + if let Value::Object(map) = json { + match map.get("@version") { + None => {}, + Some(Value::Number(version)) if version.as_f64() == Some(1.1) => {}, + Some(json_version) => panic!("unexpected JSON-LD context @version: {json_version}"), + }; + let mut bindings = Bindings::new(); + for (key, value) in map { + if key == "@version" { + // already handled above + } else if let Some('@') = key.chars().next() { + panic!("unexpected JSON-LD context key: {key}"); + } else { + let value = + json_ld::syntax::Value::from_serde_json(value.clone(), |_| metadata.clone()); + let term: Meta, M> = TryFromJson::try_from_json(value) + .expect("failed to convert {value} to term binding"); + bindings.insert( + Meta(key.clone().into(), metadata.clone()), + Meta(Nullable::Some(term.value().clone()), metadata.clone()), + ); + } + } + Definition { + base: None, + import: None, + language: None, + direction: None, + propagate: None, + protected: None, + type_: None, + version: Some(Entry::new(metadata.clone(), Meta::new(Version::V1_1, metadata))), + vocab: None, + bindings, + } + } else { + panic!("failed to convert JSON to LD context: {json:?}"); + } +} + +lazy_static! { + static ref JSON_LD_CONTEXT_DEFS: json_ld::syntax::context::Definition<()> = + construct_context_definition(&crate::context::PROV, ()); +} + +impl ExpandedJson { + async fn compact_unordered(self) -> Result { + use json_ld::{ + syntax::context, Compact, ExpandedDocument, Process, ProcessingMode, TryFromJson, + }; + + let vocabulary = no_vocabulary_mut(); + let mut loader: NoLoader = NoLoader::new(); + + // process context + let value = context::Value::One(Meta::new( + context::Context::Definition(JSON_LD_CONTEXT_DEFS.clone()), + (), + )); + let context_meta = Meta::new(value, ()); + let processed_context = context_meta + .process(vocabulary, &mut loader, None) + .await + .map_err(|e| CompactionError::JsonLd { inner: format!("{:?}", e) })?; + + // compact document + + let expanded_meta = json_ld::syntax::Value::from_serde_json(self.0, |_| ()); + + let expanded_doc: Meta, ()> = + TryFromJson::try_from_json_in(vocabulary, expanded_meta).map_err(|e| { + CompactionError::InvalidExpanded { message: format!("{:?}", e.into_value()) } + })?; + + let output = expanded_doc + .compact_full( + vocabulary, + processed_context.as_ref(), + &mut loader, + json_ld::compaction::Options { + processing_mode: ProcessingMode::JsonLd1_1, + compact_to_relative: true, + compact_arrays: true, + ordered: true, + }, + ) + .await + .map_err(|e| CompactionError::JsonLd { inner: e.to_string() })?; + + // Sort @graph + + // reference context + let json: Value = output.into_value().into(); + + if let Value::Object(mut map) = json { + map.insert( + "@context".to_string(), + Value::String("https://btp.works/chr/1.0/c.jsonld".to_string()), + ); + Ok(CompactedJson(Value::Object(map))) + } else { + Err(CompactionError::NoObject { document: json }) + } + } + + // Sort @graph by json value, as they are unstable and we need deterministic output + pub async fn compact(self) -> Result { + let mut v: serde_json::Value = + serde_json::from_str(&self.compact_unordered().await?.0.to_string())?; + + if let Some(v) = v.pointer_mut("/@graph").and_then(|p| p.as_array_mut()) { + v.sort_by_cached_key(|v| v.to_string()); + } + + Ok(v) + } + + pub async fn compact_stable_order(self) -> Result { + self.compact().await + } +} + +pub struct CompactedJson(pub serde_json::Value); + +impl core::ops::Deref for CompactedJson { + type Target = serde_json::Value; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl CompactedJson { + pub fn pretty(&self) -> String { + serde_json::to_string_pretty(&self.0).unwrap() + } +} diff --git a/crates/common/src/prov/model/json_ld/to_json_ld.rs b/crates/common/src/prov/model/json_ld/to_json_ld.rs new file mode 100644 index 000000000..1c8a0dda0 --- /dev/null +++ b/crates/common/src/prov/model/json_ld/to_json_ld.rs @@ -0,0 +1,971 @@ +use serde_json::{json, Value}; + +#[cfg(not(feature = "std"))] +use parity_scale_codec::{ + alloc::string::{String, ToString}, + alloc::vec::Vec, +}; + +use super::ExpandedJson; +use crate::prov::operations::*; +use crate::prov::ProvModel; +use crate::{ + attributes::{Attribute, Attributes}, + prov::{ + operations::{ChronicleOperation, CreateNamespace, DerivationType}, + vocab::{Chronicle, ChronicleOperations, Prov}, + ChronicleIri, ExternalIdPart, FromCompact, UuidPart, + }, +}; +pub trait ToJson { + fn to_json(&self) -> ExpandedJson; +} + +impl ToJson for ProvModel { + /// Write the model out as a JSON-LD document in expanded form + fn to_json(&self) -> ExpandedJson { + let mut doc = Vec::new(); + + for (id, ns) in self.namespaces.iter() { + doc.push(json!({ + "@id": (*id.de_compact()), + "@type": [Chronicle::Namespace.as_str()], + "http://btp.works/chronicle/ns#externalId": [{ + "@value": ns.external_id.as_str(), + }] + })) + } + + for ((_, id), agent) in self.agents.iter() { + let mut typ = vec![Prov::Agent.to_string()]; + if let Some(x) = agent.domaintypeid.as_ref() { + typ.push(x.de_compact()) + } + + if let Value::Object(mut agentdoc) = json!({ + "@id": (*id.de_compact()), + "@type": typ, + "http://btp.works/chronicle/ns#externalId": [{ + "@value": agent.external_id.as_str(), + }] + }) { + if let Some(delegation) = + self.acted_on_behalf_of.get(&(agent.namespaceid.to_owned(), id.to_owned())) + { + let mut ids = Vec::new(); + let mut qualified_ids = Vec::new(); + + for delegation in delegation.iter() { + ids.push(json!({"@id": delegation.responsible_id.de_compact()})); + qualified_ids.push(json!({"@id": delegation.id.de_compact()})); + } + + agentdoc.insert(Prov::ActedOnBehalfOf.to_string(), Value::Array(ids)); + + agentdoc + .insert(Prov::QualifiedDelegation.to_string(), Value::Array(qualified_ids)); + } + + let mut values = Vec::new(); + + values.push(json!({ + "@id": Value::String(agent.namespaceid.de_compact()), + })); + + agentdoc.insert(Chronicle::HasNamespace.to_string(), Value::Array(values)); + + Self::write_attributes(&mut agentdoc, agent.attributes.values()); + + doc.push(Value::Object(agentdoc)); + } + } + + for (_, associations) in self.association.iter() { + for association in (*associations).iter() { + if let Value::Object(mut associationdoc) = json!({ + "@id": association.id.de_compact(), + "@type": [Prov::Association.as_str()], + }) { + let mut values = Vec::new(); + + values.push(json!({ + "@id": Value::String(association.agent_id.de_compact()), + })); + + associationdoc.insert(Prov::Responsible.to_string(), Value::Array(values)); + + associationdoc.insert( + Prov::HadActivity.to_string(), + Value::Array(vec![json!({ + "@id": Value::String(association.activity_id.de_compact()), + })]), + ); + + if let Some(role) = &association.role { + associationdoc.insert( + Prov::HadRole.to_string(), + json!([{ "@value": role.to_string()}]), + ); + } + + let mut values = Vec::new(); + + values.push(json!({ + "@id": Value::String(association.namespace_id.de_compact()), + })); + + associationdoc + .insert(Chronicle::HasNamespace.to_string(), Value::Array(values)); + + doc.push(Value::Object(associationdoc)); + } + } + } + + for (_, attributions) in self.attribution.iter() { + for attribution in (*attributions).iter() { + if let Value::Object(mut attribution_doc) = json!({ + "@id": attribution.id.de_compact(), + "@type": [Prov::Attribution.as_str()], + }) { + let mut values = Vec::new(); + + values.push(json!({ + "@id": Value::String(attribution.agent_id.de_compact()), + })); + + attribution_doc.insert(Prov::Responsible.to_string(), Value::Array(values)); + + attribution_doc.insert( + Prov::HadEntity.to_string(), + Value::Array(vec![json!({ + "@id": Value::String(attribution.entity_id.de_compact()), + })]), + ); + + if let Some(role) = &attribution.role { + attribution_doc.insert( + Prov::HadRole.to_string(), + json!([{ "@value": role.to_string()}]), + ); + } + + let mut values = Vec::new(); + + values.push(json!({ + "@id": Value::String(attribution.namespace_id.de_compact()), + })); + + attribution_doc + .insert(Chronicle::HasNamespace.to_string(), Value::Array(values)); + + doc.push(Value::Object(attribution_doc)); + } + } + } + + for (_, delegations) in self.delegation.iter() { + for delegation in (*delegations).iter() { + if let Value::Object(mut delegationdoc) = json!({ + "@id": delegation.id.de_compact(), + "@type": [Prov::Delegation.as_str()], + }) { + if let Some(activity_id) = &delegation.activity_id { + delegationdoc.insert( + Prov::HadActivity.to_string(), + Value::Array(vec![json!({ + "@id": Value::String(activity_id.de_compact()), + })]), + ); + } + + if let Some(role) = &delegation.role { + delegationdoc.insert( + Prov::HadRole.to_string(), + json!([{ "@value": role.to_string()}]), + ); + } + + let mut responsible_ids = Vec::new(); + responsible_ids.push( + json!({ "@id": Value::String(delegation.responsible_id.de_compact())}), + ); + + delegationdoc + .insert(Prov::ActedOnBehalfOf.to_string(), Value::Array(responsible_ids)); + + let mut delegate_ids = Vec::new(); + delegate_ids + .push(json!({ "@id": Value::String(delegation.delegate_id.de_compact())})); + + delegationdoc.insert(Prov::Delegate.to_string(), Value::Array(delegate_ids)); + + let mut values = Vec::new(); + + values.push(json!({ + "@id": Value::String(delegation.namespace_id.de_compact()), + })); + + delegationdoc.insert(Chronicle::HasNamespace.to_string(), Value::Array(values)); + + doc.push(Value::Object(delegationdoc)); + } + } + } + + for ((namespace, id), activity) in self.activities.iter() { + let mut typ = vec![Prov::Activity.de_compact()]; + if let Some(x) = activity.domaintypeid.as_ref() { + typ.push(x.de_compact()) + } + + if let Value::Object(mut activitydoc) = json!({ + "@id": (*id.de_compact()), + "@type": typ, + "http://btp.works/chronicle/ns#externalId": [{ + "@value": activity.external_id.as_str(), + }] + }) { + if let Some(time) = activity.started { + let mut values = Vec::new(); + values.push(json!({"@value": time.to_rfc3339()})); + + activitydoc.insert( + "http://www.w3.org/ns/prov#startedAtTime".to_string(), + Value::Array(values), + ); + } + + if let Some(time) = activity.ended { + let mut values = Vec::new(); + values.push(json!({"@value": time.to_rfc3339()})); + + activitydoc.insert( + "http://www.w3.org/ns/prov#endedAtTime".to_string(), + Value::Array(values), + ); + } + + if let Some(asoc) = self.association.get(&(namespace.to_owned(), id.to_owned())) { + let mut ids = Vec::new(); + + let mut qualified_ids = Vec::new(); + for asoc in asoc.iter() { + ids.push(json!({"@id": asoc.agent_id.de_compact()})); + qualified_ids.push(json!({"@id": asoc.id.de_compact()})); + } + + activitydoc.insert(Prov::WasAssociatedWith.de_compact(), Value::Array(ids)); + + activitydoc.insert( + Prov::QualifiedAssociation.de_compact(), + Value::Array(qualified_ids), + ); + } + + if let Some(usage) = self.usage.get(&(namespace.to_owned(), id.to_owned())) { + let mut ids = Vec::new(); + + for usage in usage.iter() { + ids.push(json!({"@id": usage.entity_id.de_compact()})); + } + + activitydoc.insert(Prov::Used.de_compact(), Value::Array(ids)); + } + + let mut values = Vec::new(); + + values.push(json!({ + "@id": Value::String(activity.namespaceid.de_compact()), + })); + + activitydoc.insert(Chronicle::HasNamespace.to_string(), Value::Array(values)); + + if let Some(activities) = + self.was_informed_by.get(&(namespace.to_owned(), id.to_owned())) + { + let mut values = Vec::new(); + + for (_, activity) in (*activities).iter() { + values.push(json!({ + "@id": Value::String(activity.de_compact()), + })); + } + activitydoc.insert(Prov::WasInformedBy.to_string(), Value::Array(values)); + } + + Self::write_attributes(&mut activitydoc, activity.attributes.values()); + + doc.push(Value::Object(activitydoc)); + } + } + + for ((namespace, id), entity) in self.entities.iter() { + let mut typ = vec![Prov::Entity.de_compact()]; + if let Some(x) = entity.domaintypeid.as_ref() { + typ.push(x.de_compact()) + } + + if let Value::Object(mut entitydoc) = json!({ + "@id": (*id.de_compact()), + "@type": typ, + "http://btp.works/chronicle/ns#externalId": [{ + "@value": entity.external_id.as_str() + }] + }) { + if let Some(derivation) = + self.derivation.get(&(namespace.to_owned(), id.to_owned())) + { + let mut derived_ids = Vec::new(); + let mut primary_ids = Vec::new(); + let mut quotation_ids = Vec::new(); + let mut revision_ids = Vec::new(); + + for derivation in derivation.iter() { + let id = json!({"@id": derivation.used_id.de_compact()}); + match derivation.typ { + DerivationType::PrimarySource => primary_ids.push(id), + DerivationType::Quotation => quotation_ids.push(id), + DerivationType::Revision => revision_ids.push(id), + DerivationType::None => derived_ids.push(id), + } + } + if !derived_ids.is_empty() { + entitydoc + .insert(Prov::WasDerivedFrom.to_string(), Value::Array(derived_ids)); + } + if !primary_ids.is_empty() { + entitydoc + .insert(Prov::HadPrimarySource.to_string(), Value::Array(primary_ids)); + } + if !quotation_ids.is_empty() { + entitydoc + .insert(Prov::WasQuotedFrom.to_string(), Value::Array(quotation_ids)); + } + if !revision_ids.is_empty() { + entitydoc + .insert(Prov::WasRevisionOf.to_string(), Value::Array(revision_ids)); + } + } + + if let Some(generation) = + self.generation.get(&(namespace.to_owned(), id.to_owned())) + { + let mut ids = Vec::new(); + + for generation in generation.iter() { + ids.push(json!({"@id": generation.activity_id.de_compact()})); + } + + entitydoc.insert(Prov::WasGeneratedBy.to_string(), Value::Array(ids)); + } + + let entity_key = (entity.namespaceid.clone(), entity.id.clone()); + + if let Some(attributions) = self.attribution.get(&entity_key) { + let mut ids = Vec::new(); + + let mut qualified_ids = Vec::new(); + for attribution in attributions.iter() { + ids.push(json!({"@id": attribution.agent_id.de_compact()})); + qualified_ids.push(json!({"@id": attribution.id.de_compact()})); + } + + entitydoc.insert(Prov::WasAttributedTo.de_compact(), Value::Array(ids)); + + entitydoc.insert( + Prov::QualifiedAttribution.de_compact(), + Value::Array(qualified_ids), + ); + } + + let mut values = Vec::new(); + + values.push(json!({ + "@id": Value::String(entity.namespaceid.de_compact()), + })); + + entitydoc.insert(Chronicle::HasNamespace.to_string(), Value::Array(values)); + + Self::write_attributes(&mut entitydoc, entity.attributes.values()); + + doc.push(Value::Object(entitydoc)); + } + } + + ExpandedJson(Value::Array(doc)) + } +} + +impl ProvModel { + fn write_attributes<'a, I: Iterator>( + doc: &mut serde_json::Map, + attributes: I, + ) { + let mut attribute_node = serde_json::Map::new(); + + for attribute in attributes { + attribute_node.insert(attribute.typ.clone(), attribute.value.0.clone()); + } + + doc.insert( + Chronicle::Value.to_string(), + json!([{"@value" : Value::Object(attribute_node), "@type": "@json"}]), + ); + } +} + +impl ToJson for ChronicleOperation { + fn to_json(&self) -> ExpandedJson { + let mut operation: Vec = Vec::new(); + + let o = match self { + ChronicleOperation::CreateNamespace(CreateNamespace { id, .. }) => { + let mut o = Value::new_operation(ChronicleOperations::CreateNamespace); + + o.has_value( + OperationValue::string(id.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(id.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o + }, + ChronicleOperation::AgentExists(AgentExists { namespace, external_id }) => { + let mut o = Value::new_operation(ChronicleOperations::AgentExists); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value(OperationValue::string(external_id), ChronicleOperations::AgentName); + + o + }, + ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf { + namespace, + id: _, // This is derivable from components + delegate_id, + activity_id, + role, + responsible_id, + }) => { + let mut o = Value::new_operation(ChronicleOperations::AgentActsOnBehalfOf); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(delegate_id.external_id_part()), + ChronicleOperations::DelegateId, + ); + + o.has_value( + OperationValue::string(responsible_id.external_id_part()), + ChronicleOperations::ResponsibleId, + ); + + if let Some(role) = role { + o.has_value(OperationValue::string(role), ChronicleOperations::Role); + } + + if let Some(activity_id) = activity_id { + o.has_value( + OperationValue::string(activity_id.external_id_part()), + ChronicleOperations::ActivityName, + ); + } + + o + }, + ChronicleOperation::ActivityExists(ActivityExists { namespace, external_id }) => { + let mut o = Value::new_operation(ChronicleOperations::ActivityExists); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value(OperationValue::string(external_id), ChronicleOperations::ActivityName); + + o + }, + ChronicleOperation::StartActivity(StartActivity { namespace, id, time }) => { + let mut o = Value::new_operation(ChronicleOperations::StartActivity); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(id.external_id_part()), + ChronicleOperations::ActivityName, + ); + + o.has_value( + OperationValue::string(time.to_rfc3339()), + ChronicleOperations::StartActivityTime, + ); + + o + }, + ChronicleOperation::EndActivity(EndActivity { namespace, id, time }) => { + let mut o = Value::new_operation(ChronicleOperations::EndActivity); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(id.external_id_part()), + ChronicleOperations::ActivityName, + ); + + o.has_value( + OperationValue::string(time.to_rfc3339()), + ChronicleOperations::EndActivityTime, + ); + + o + }, + ChronicleOperation::ActivityUses(ActivityUses { namespace, id, activity }) => { + let mut o = Value::new_operation(ChronicleOperations::ActivityUses); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(id.external_id_part()), + ChronicleOperations::EntityName, + ); + + o.has_value( + OperationValue::string(activity.external_id_part()), + ChronicleOperations::ActivityName, + ); + + o + }, + ChronicleOperation::EntityExists(EntityExists { namespace, external_id }) => { + let mut o = Value::new_operation(ChronicleOperations::EntityExists); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value(OperationValue::string(external_id), ChronicleOperations::EntityName); + + o + }, + ChronicleOperation::WasGeneratedBy(WasGeneratedBy { namespace, id, activity }) => { + let mut o = Value::new_operation(ChronicleOperations::WasGeneratedBy); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(id.external_id_part()), + ChronicleOperations::EntityName, + ); + + o.has_value( + OperationValue::string(activity.external_id_part()), + ChronicleOperations::ActivityName, + ); + + o + }, + ChronicleOperation::WasInformedBy(WasInformedBy { + namespace, + activity, + informing_activity, + }) => { + let mut o = Value::new_operation(ChronicleOperations::WasInformedBy); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(activity.external_id_part()), + ChronicleOperations::ActivityName, + ); + + o.has_value( + OperationValue::string(informing_activity.external_id_part()), + ChronicleOperations::InformingActivityName, + ); + + o + }, + ChronicleOperation::EntityDerive(EntityDerive { + namespace, + id, + used_id, + activity_id, + typ, + }) => { + let mut o = Value::new_operation(ChronicleOperations::EntityDerive); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(id.external_id_part()), + ChronicleOperations::EntityName, + ); + + o.has_value( + OperationValue::string(used_id.external_id_part()), + ChronicleOperations::UsedEntityName, + ); + + if let Some(activity) = activity_id { + o.has_value( + OperationValue::string(activity.external_id_part()), + ChronicleOperations::ActivityName, + ); + } + + if *typ != DerivationType::None { + o.derivation(typ); + } + + o + }, + ChronicleOperation::SetAttributes(SetAttributes::Entity { + namespace, + id, + attributes, + }) => { + let mut o = Value::new_operation(ChronicleOperations::SetAttributes); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(id.external_id_part()), + ChronicleOperations::EntityName, + ); + + if let Some(domaintypeid) = &attributes.typ { + let id = OperationValue::string(domaintypeid.external_id_part()); + o.has_value(id, ChronicleOperations::DomaintypeId); + } + + o.attributes_object(attributes); + + o + }, + ChronicleOperation::SetAttributes(SetAttributes::Activity { + namespace, + id, + attributes, + }) => { + let mut o = Value::new_operation(ChronicleOperations::SetAttributes); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(id.external_id_part()), + ChronicleOperations::ActivityName, + ); + + if let Some(domaintypeid) = &attributes.typ { + let id = OperationValue::string(domaintypeid.external_id_part()); + o.has_value(id, ChronicleOperations::DomaintypeId); + } + + o.attributes_object(attributes); + + o + }, + ChronicleOperation::SetAttributes(SetAttributes::Agent { + namespace, + id, + attributes, + }) => { + let mut o = Value::new_operation(ChronicleOperations::SetAttributes); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(id.external_id_part()), + ChronicleOperations::AgentName, + ); + + if let Some(domaintypeid) = &attributes.typ { + let id = OperationValue::string(domaintypeid.external_id_part()); + o.has_value(id, ChronicleOperations::DomaintypeId); + } + + o.attributes_object(attributes); + + o + }, + ChronicleOperation::WasAssociatedWith(WasAssociatedWith { + id: _, + role, + namespace, + activity_id, + agent_id, + }) => { + let mut o = Value::new_operation(ChronicleOperations::WasAssociatedWith); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(activity_id.external_id_part()), + ChronicleOperations::ActivityName, + ); + + o.has_value( + OperationValue::string(agent_id.external_id_part()), + ChronicleOperations::AgentName, + ); + + if let Some(role) = role { + o.has_value(OperationValue::string(role), ChronicleOperations::Role); + } + + o + }, + ChronicleOperation::WasAttributedTo(WasAttributedTo { + id: _, + role, + namespace, + entity_id, + agent_id, + }) => { + let mut o = Value::new_operation(ChronicleOperations::WasAttributedTo); + + o.has_value( + OperationValue::string(namespace.external_id_part()), + ChronicleOperations::NamespaceName, + ); + + o.has_value( + OperationValue::string(namespace.uuid_part()), + ChronicleOperations::NamespaceUuid, + ); + + o.has_value( + OperationValue::string(entity_id.external_id_part()), + ChronicleOperations::EntityName, + ); + + o.has_value( + OperationValue::string(agent_id.external_id_part()), + ChronicleOperations::AgentName, + ); + + if let Some(role) = role { + o.has_value(OperationValue::string(role), ChronicleOperations::Role); + } + + o + }, + }; + operation.push(o); + super::ExpandedJson(operation.into()) + } +} + +struct OperationValue(String); + +impl OperationValue { + fn string(value: impl ToString) -> Self { + OperationValue(value.to_string()) + } + + #[allow(dead_code)] + fn identity(id: ChronicleIri) -> Self { + OperationValue(id.to_string()) + } +} + +trait Operate { + fn new_operation(op: ChronicleOperations) -> Self; + fn new_type(id: OperationValue, op: ChronicleOperations) -> Self; + fn new_value(id: OperationValue) -> Self; + fn new_id(id: OperationValue) -> Self; + fn has_value(&mut self, value: OperationValue, op: ChronicleOperations); + fn has_id(&mut self, id: OperationValue, op: ChronicleOperations); + fn attributes_object(&mut self, attributes: &Attributes); + fn derivation(&mut self, typ: &DerivationType); +} + +impl Operate for Value { + fn new_type(id: OperationValue, op: ChronicleOperations) -> Self { + json!({ + "@id": id.0, + "@type": [op.as_str()], + }) + } + + fn new_value(id: OperationValue) -> Self { + json!({ + "@value": id.0 + }) + } + + fn new_id(id: OperationValue) -> Self { + json!({ + "@id": id.0 + }) + } + + fn has_value(&mut self, value: OperationValue, op: ChronicleOperations) { + if let Value::Object(map) = self { + let key = op.to_string(); + let mut values: Vec = Vec::new(); + let object = Self::new_value(value); + values.push(object); + map.insert(key, Value::Array(values)); + } else { + panic!("use on JSON objects only"); + } + } + + fn has_id(&mut self, id: OperationValue, op: ChronicleOperations) { + if let Value::Object(map) = self { + let key = op.to_string(); + let mut value: Vec = Vec::new(); + let object = Self::new_id(id); + value.push(object); + map.insert(key, Value::Array(value)); + } else { + panic!("use on JSON objects only"); + } + } + + fn new_operation(op: ChronicleOperations) -> Self { + let id = OperationValue::string("_:n1"); + Self::new_type(id, op) + } + + fn attributes_object(&mut self, attributes: &Attributes) { + if let Value::Object(map) = self { + let mut attribute_node = serde_json::Map::new(); + for attribute in attributes.attributes.values() { + attribute_node.insert(attribute.typ.clone(), attribute.value.0.clone()); + } + map.insert( + ChronicleOperations::Attributes.to_string(), + json!([{"@value" : attribute_node, "@type": "@json"}]), + ); + } else { + panic!("use on JSON objects only"); + } + } + + fn derivation(&mut self, typ: &DerivationType) { + let typ = match typ { + DerivationType::None => panic!("should never handle a None derivation type"), + DerivationType::Revision => "Revision", + DerivationType::Quotation => "Quotation", + DerivationType::PrimarySource => "PrimarySource", + }; + let id = OperationValue::string(typ); + + self.has_value(id, ChronicleOperations::DerivationType); + } +} diff --git a/crates/common/src/prov/model/mod.rs b/crates/common/src/prov/model/mod.rs index bae0eed8e..9b48f76a5 100644 --- a/crates/common/src/prov/model/mod.rs +++ b/crates/common/src/prov/model/mod.rs @@ -1,483 +1,465 @@ mod contradiction; pub use contradiction::Contradiction; +#[cfg(feature = "json-ld")] +pub mod json_ld; pub mod transaction; -use scale_info::prelude::sync::Arc; -use scale_info::{build::Fields, Path, Type, TypeInfo}; -pub use transaction::ChronicleTransaction; -use chrono::{DateTime, NaiveDateTime, Utc}; -use iref::IriBuf; -use json_ld::NoLoader; -use lazy_static::lazy_static; -use locspan::Meta; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use rdf_types::{vocabulary::no_vocabulary_mut, BlankIdBuf}; +use parity_scale_codec::{Decode, Encode}; + +use core::{convert::Infallible, fmt::Debug}; +#[cfg(not(feature = "std"))] +use parity_scale_codec::{ + alloc::collections::{BTreeMap, BTreeSet}, + alloc::string::String, + alloc::vec::Vec, +}; +use scale_info::TypeInfo; +#[cfg(not(feature = "std"))] +use scale_info::{ + prelude::borrow::ToOwned, prelude::string::ToString, prelude::sync::Arc, prelude::*, +}; use serde::Serialize; -use serde_json::Value; +#[cfg(feature = "std")] use std::{ - collections::{BTreeMap, BTreeSet}, - convert::Infallible, - fmt::{Debug, Display}, + collections::{BTreeMap, BTreeSet}, + sync::Arc, }; -use tokio::task::JoinError; + +#[cfg(feature = "std")] +use thiserror::Error; +#[cfg(not(feature = "std"))] +use thiserror_no_std::Error; + use tracing::{instrument, trace}; +pub use transaction::ChronicleTransaction; use uuid::Uuid; use crate::{ - attributes::{Attribute, Attributes}, - identity::IdentityError, - opa::OpaExecutorError, - prov::operations::WasAttributedTo, + attributes::{Attribute, Attributes}, + identity::IdentityError, + prov::operations::WasAttributedTo, }; -use super::operations::TimeWrapper; -use super::UuidWrapper; use super::{ - id, - operations::{ - ActivityExists, ActivityUses, ActsOnBehalfOf, AgentExists, ChronicleOperation, - CreateNamespace, DerivationType, EndActivity, EntityDerive, EntityExists, SetAttributes, - StartActivity, WasAssociatedWith, WasGeneratedBy, WasInformedBy, - }, - ActivityId, AgentId, AssociationId, AttributionId, ChronicleIri, DelegationId, DomaintypeId, - EntityId, ExternalId, ExternalIdPart, NamespaceId, Role, UuidPart, + id, + operations::{ + ActivityExists, ActivityUses, ActsOnBehalfOf, AgentExists, ChronicleOperation, + CreateNamespace, DerivationType, EndActivity, EntityDerive, EntityExists, SetAttributes, + StartActivity, TimeWrapper, WasAssociatedWith, WasGeneratedBy, WasInformedBy, + }, + ActivityId, AgentId, AssociationId, AttributionId, ChronicleIri, DelegationId, DomaintypeId, + EntityId, ExternalId, ExternalIdPart, NamespaceId, Role, UuidPart, UuidWrapper, }; -pub mod to_json_ld; - -use thiserror::Error; - #[derive(Error, Debug)] pub enum ProcessorError { - #[error("Invalid address")] - Address, - #[error("Json Ld Error {0}")] - Compaction(#[from] CompactionError), - #[error("Contradiction {0}")] - Contradiction(#[from] Contradiction), - #[error("Json Ld Error {inner}")] - Expansion { inner: String }, - #[error("IdentityError {0}")] - Identity(#[from] IdentityError), - #[error("Invalid IRI {0}")] - IRef(#[from] iref::Error), - #[error("Not a Chronicle IRI {0}")] - NotAChronicleIri(#[from] id::ParseIriError), - #[error("Missing @id {object:?}")] - MissingId { object: serde_json::Value }, - #[error("Missing property {iri}:{object:?}")] - MissingProperty { - iri: String, - object: serde_json::Value, - }, - #[error("Json LD object is not a node {0}")] - NotANode(serde_json::Value), - #[error("Chronicle value is not a JSON object")] - NotAnObject, - #[error("OpaExecutorError: {0}")] - OpaExecutor(#[from] OpaExecutorError), - #[error("Malformed JSON {0}")] - SerdeJson(#[from] serde_json::Error), - #[error("Unparsable date/time {0}")] - SubmissionFormat(#[from] PayloadError), - #[error("Submission body format: {0}")] - Time(#[from] chrono::ParseError), - #[error("Tokio Error {0}")] - Tokio(#[from] JoinError), - #[error("State is not valid utf8 {0}")] - Utf8(#[from] std::str::Utf8Error), + #[error("Invalid address")] + Address, + #[cfg(feature = "json-ld")] + #[error("Json Ld Error {0}")] + Compaction(#[from] json_ld::CompactionError), + #[error("Contradiction {0}")] + Contradiction(Contradiction), + #[error("Json Ld Error {inner}")] + Expansion { inner: String }, + #[error("IdentityError {0}")] + Identity(#[from] IdentityError), + #[cfg(feature = "json-ld")] + #[error("Invalid IRI {0}")] + IRef(#[from] iref::Error), + #[error("Not a Chronicle IRI {0}")] + NotAChronicleIri(#[from] id::ParseIriError), + #[error("Missing @id {object:?}")] + MissingId { object: serde_json::Value }, + #[error("Missing property {iri}:{object:?}")] + MissingProperty { iri: String, object: serde_json::Value }, + #[error("Json LD object is not a node {0}")] + NotANode(serde_json::Value), + #[error("Chronicle value is not a JSON object")] + NotAnObject, + #[error("OpaExecutorError: {0}")] + OpaExecutor(#[from] anyhow::Error), + #[error("Malformed JSON {0}")] + SerdeJson(#[from] serde_json::Error), + #[error("Unparsable date/time {0}")] + SubmissionFormat(#[from] PayloadError), + #[error("Submission body format: {0}")] + Time(#[from] chrono::ParseError), + #[error("Tokio Error")] + Tokio, + #[error("State is not valid utf8 {0}")] + Utf8(#[from] core::str::Utf8Error), } #[derive(Error, Debug)] pub enum PayloadError { - #[error("No list of Chronicle operations")] - OpsNotAList, - #[error("Not a JSON object")] - NotAnObject, - #[error("No version number")] - VersionMissing, - #[error("Unknown version number")] - VersionUnknown, + #[error("No list of Chronicle operations")] + OpsNotAList, + #[error("Not a JSON object")] + NotAnObject, + #[error("No version number")] + VersionMissing, + #[error("Unknown version number")] + VersionUnknown, } impl From for ProcessorError { - fn from(_: Infallible) -> Self { - unreachable!() - } + fn from(_: Infallible) -> Self { + unreachable!() + } } #[derive(Error, Debug)] pub enum ChronicleTransactionIdError { - #[error("Invalid transaction id {id}")] - InvalidTransactionId { id: String }, + #[error("Invalid transaction id {id}")] + InvalidTransactionId { id: String }, } #[derive(Serialize, Deserialize, Encode, Decode, PartialEq, Eq, Debug, Clone)] pub struct ChronicleTransactionId(String); -impl Display for ChronicleTransactionId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } +impl core::fmt::Display for ChronicleTransactionId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(&self.0) + } } impl From for ChronicleTransactionId { - fn from(u: Uuid) -> Self { - Self(u.to_string()) - } + fn from(u: Uuid) -> Self { + Self(u.to_string()) + } } impl From<&str> for ChronicleTransactionId { - fn from(s: &str) -> Self { - Self(s.to_owned()) - } + fn from(s: &str) -> Self { + Self(s.to_owned()) + } } impl ChronicleTransactionId { - pub fn as_str(&self) -> &str { - &self.0 - } + pub fn as_str(&self) -> &str { + &self.0 + } } #[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq, Serialize, Deserialize)] pub struct Namespace { - pub id: NamespaceId, - pub uuid: UuidWrapper, - pub external_id: ExternalId, + pub id: NamespaceId, + pub uuid: UuidWrapper, + pub external_id: ExternalId, } impl Namespace { - pub fn new(id: NamespaceId, uuid: Uuid, external_id: &ExternalId) -> Self { - Self { - id, - uuid: uuid.into(), - external_id: external_id.to_owned(), - } - } + pub fn new(id: NamespaceId, uuid: Uuid, external_id: &ExternalId) -> Self { + Self { id, uuid: uuid.into(), external_id: external_id.to_owned() } + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Encode, Decode, TypeInfo)] pub struct Agent { - pub id: AgentId, - pub namespaceid: NamespaceId, - pub external_id: ExternalId, - pub domaintypeid: Option, - pub attributes: BTreeMap, + pub id: AgentId, + pub namespaceid: NamespaceId, + pub external_id: ExternalId, + pub domaintypeid: Option, + pub attributes: BTreeMap, } impl Agent { - pub fn has_attributes(self, attributes: Attributes) -> Self { - let Self { - id, - namespaceid, - external_id, - .. - } = self; - - Self { - id, - namespaceid, - external_id, - domaintypeid: attributes.typ, - attributes: attributes.attributes, - } - } - - // Create a prototypical agent from its IRI, we can only determine external_id - pub fn exists(namespaceid: NamespaceId, id: AgentId) -> Self { - Self { - namespaceid, - external_id: id.external_id_part().to_owned(), - id, - domaintypeid: None, - attributes: BTreeMap::new(), - } - } + pub fn has_attributes(self, attributes: Attributes) -> Self { + let Self { id, namespaceid, external_id, .. } = self; + + Self { + id, + namespaceid, + external_id, + domaintypeid: attributes.typ, + attributes: attributes.attributes, + } + } + + // Create a prototypical agent from its IRI, we can only determine external_id + pub fn exists(namespaceid: NamespaceId, id: AgentId) -> Self { + Self { + namespaceid, + external_id: id.external_id_part().to_owned(), + id, + domaintypeid: None, + attributes: BTreeMap::new(), + } + } } #[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq, Serialize, Deserialize)] pub struct Activity { - pub id: ActivityId, - pub namespaceid: NamespaceId, - pub external_id: ExternalId, - pub domaintypeid: Option, - pub attributes: BTreeMap, - pub started: Option, - pub ended: Option, + pub id: ActivityId, + pub namespaceid: NamespaceId, + pub external_id: ExternalId, + pub domaintypeid: Option, + pub attributes: BTreeMap, + pub started: Option, + pub ended: Option, } impl Activity { - pub fn has_attributes(self, attributes: Attributes) -> Self { - let Self { - id, - namespaceid, - external_id, - started, - ended, - .. - } = self; - Self { - id, - namespaceid, - external_id, - started, - ended, - domaintypeid: attributes.typ, - attributes: attributes.attributes, - } - } - - // Create a prototypical agent from its IRI, we can only determine external_id - pub fn exists(namespaceid: NamespaceId, id: ActivityId) -> Self { - Self { - namespaceid, - external_id: id.external_id_part().to_owned(), - id, - started: None, - ended: None, - domaintypeid: None, - attributes: BTreeMap::new(), - } - } + pub fn has_attributes(self, attributes: Attributes) -> Self { + let Self { id, namespaceid, external_id, started, ended, .. } = self; + Self { + id, + namespaceid, + external_id, + started, + ended, + domaintypeid: attributes.typ, + attributes: attributes.attributes, + } + } + + // Create a prototypical agent from its IRI, we can only determine external_id + pub fn exists(namespaceid: NamespaceId, id: ActivityId) -> Self { + Self { + namespaceid, + external_id: id.external_id_part().to_owned(), + id, + started: None, + ended: None, + domaintypeid: None, + attributes: BTreeMap::new(), + } + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Encode, Decode, TypeInfo)] pub struct Entity { - pub id: EntityId, - pub namespaceid: NamespaceId, - pub external_id: ExternalId, - pub domaintypeid: Option, - pub attributes: BTreeMap, + pub id: EntityId, + pub namespaceid: NamespaceId, + pub external_id: ExternalId, + pub domaintypeid: Option, + pub attributes: BTreeMap, } impl Entity { - pub fn has_attributes(self, attributes: Attributes) -> Self { - let Self { - id, - namespaceid, - external_id, - .. - } = self; - Self { - id, - namespaceid, - external_id, - domaintypeid: attributes.typ, - attributes: attributes.attributes, - } - } - - pub fn exists(namespaceid: NamespaceId, id: EntityId) -> Self { - Self { - external_id: id.external_id_part().to_owned(), - id, - namespaceid, - domaintypeid: None, - attributes: BTreeMap::new(), - } - } + pub fn has_attributes(self, attributes: Attributes) -> Self { + let Self { id, namespaceid, external_id, .. } = self; + Self { + id, + namespaceid, + external_id, + domaintypeid: attributes.typ, + attributes: attributes.attributes, + } + } + + pub fn exists(namespaceid: NamespaceId, id: EntityId) -> Self { + Self { + external_id: id.external_id_part().to_owned(), + id, + namespaceid, + domaintypeid: None, + attributes: BTreeMap::new(), + } + } } #[derive( - Debug, - Clone, - PartialEq, - Eq, - Hash, - Ord, - PartialOrd, - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, + Debug, + Clone, + PartialEq, + Eq, + Hash, + Ord, + PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, )] pub struct Derivation { - pub generated_id: EntityId, - pub used_id: EntityId, - pub activity_id: Option, - pub typ: DerivationType, + pub generated_id: EntityId, + pub used_id: EntityId, + pub activity_id: Option, + pub typ: DerivationType, } #[derive( - Debug, - Clone, - PartialEq, - Eq, - Hash, - Ord, - PartialOrd, - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, + Debug, + Clone, + PartialEq, + Eq, + Hash, + Ord, + PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, )] pub struct Delegation { - pub namespace_id: NamespaceId, - pub id: DelegationId, - pub delegate_id: AgentId, - pub responsible_id: AgentId, - pub activity_id: Option, - pub role: Option, + pub namespace_id: NamespaceId, + pub id: DelegationId, + pub delegate_id: AgentId, + pub responsible_id: AgentId, + pub activity_id: Option, + pub role: Option, } impl Delegation { - pub fn new( - namespace_id: &NamespaceId, - delegate_id: &AgentId, - responsible_id: &AgentId, - activity_id: Option<&ActivityId>, - role: Option, - ) -> Self { - Self { - namespace_id: namespace_id.clone(), - id: DelegationId::from_component_ids( - delegate_id, - responsible_id, - activity_id, - role.as_ref(), - ), - delegate_id: delegate_id.clone(), - responsible_id: responsible_id.clone(), - activity_id: activity_id.cloned(), - role, - } - } + pub fn new( + namespace_id: &NamespaceId, + delegate_id: &AgentId, + responsible_id: &AgentId, + activity_id: Option<&ActivityId>, + role: Option, + ) -> Self { + Self { + namespace_id: namespace_id.clone(), + id: DelegationId::from_component_ids( + delegate_id, + responsible_id, + activity_id, + role.as_ref(), + ), + delegate_id: delegate_id.clone(), + responsible_id: responsible_id.clone(), + activity_id: activity_id.cloned(), + role, + } + } } #[derive( - Debug, - Clone, - PartialEq, - Eq, - Hash, - Ord, - PartialOrd, - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, + Debug, + Clone, + PartialEq, + Eq, + Hash, + Ord, + PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, )] pub struct Association { - pub namespace_id: NamespaceId, - pub id: AssociationId, - pub agent_id: AgentId, - pub activity_id: ActivityId, - pub role: Option, + pub namespace_id: NamespaceId, + pub id: AssociationId, + pub agent_id: AgentId, + pub activity_id: ActivityId, + pub role: Option, } impl Association { - pub fn new( - namespace_id: &NamespaceId, - agent_id: &AgentId, - activity_id: &ActivityId, - role: Option, - ) -> Self { - Self { - namespace_id: namespace_id.clone(), - id: AssociationId::from_component_ids(agent_id, activity_id, role.as_ref()), - agent_id: agent_id.clone(), - activity_id: activity_id.clone(), - role, - } - } + pub fn new( + namespace_id: &NamespaceId, + agent_id: &AgentId, + activity_id: &ActivityId, + role: Option, + ) -> Self { + Self { + namespace_id: namespace_id.clone(), + id: AssociationId::from_component_ids(agent_id, activity_id, role.as_ref()), + agent_id: agent_id.clone(), + activity_id: activity_id.clone(), + role, + } + } } #[derive( - Debug, - Clone, - PartialEq, - Eq, - Hash, - Ord, - PartialOrd, - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, + Debug, + Clone, + PartialEq, + Eq, + Hash, + Ord, + PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, )] pub struct Usage { - pub activity_id: ActivityId, - pub entity_id: EntityId, + pub activity_id: ActivityId, + pub entity_id: EntityId, } #[derive( - Debug, - Clone, - PartialEq, - Eq, - Hash, - Ord, - PartialOrd, - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, + Debug, + Clone, + PartialEq, + Eq, + Hash, + Ord, + PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, )] pub struct Generation { - pub activity_id: ActivityId, - pub generated_id: EntityId, + pub activity_id: ActivityId, + pub generated_id: EntityId, } #[derive( - Debug, - Clone, - PartialEq, - Eq, - Hash, - PartialOrd, - Ord, - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, + Debug, + Clone, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, )] pub struct GeneratedEntity { - pub entity_id: EntityId, - pub generated_id: ActivityId, + pub entity_id: EntityId, + pub generated_id: ActivityId, } #[derive( - Debug, - Clone, - PartialEq, - Eq, - Hash, - Ord, - PartialOrd, - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, + Debug, + Clone, + PartialEq, + Eq, + Hash, + Ord, + PartialOrd, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, )] pub struct Attribution { - pub namespace_id: NamespaceId, - pub id: AttributionId, - pub agent_id: AgentId, - pub entity_id: EntityId, - pub role: Option, + pub namespace_id: NamespaceId, + pub id: AttributionId, + pub agent_id: AgentId, + pub entity_id: EntityId, + pub role: Option, } impl Attribution { - pub fn new( - namespace_id: &NamespaceId, - agent_id: &AgentId, - entity_id: &EntityId, - role: Option, - ) -> Self { - Self { - namespace_id: namespace_id.clone(), - id: AttributionId::from_component_ids(agent_id, entity_id, role.as_ref()), - agent_id: agent_id.clone(), - entity_id: entity_id.clone(), - role, - } - } + pub fn new( + namespace_id: &NamespaceId, + agent_id: &AgentId, + entity_id: &EntityId, + role: Option, + ) -> Self { + Self { + namespace_id: namespace_id.clone(), + id: AttributionId::from_component_ids(agent_id, entity_id, role.as_ref()), + agent_id: agent_id.clone(), + entity_id: entity_id.clone(), + role, + } + } } type NamespacedId = (NamespaceId, T); @@ -487,882 +469,643 @@ type NamespacedActivity = NamespacedId; #[derive(Debug, Default, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] pub struct ProvModel { - pub namespaces: BTreeMap>, - pub agents: BTreeMap>, - pub acted_on_behalf_of: BTreeMap>>, - pub delegation: BTreeMap>>, - pub entities: BTreeMap>, - pub derivation: BTreeMap>>, - pub generation: BTreeMap>>, - pub attribution: BTreeMap>>, - pub activities: BTreeMap>, - pub was_informed_by: BTreeMap>>, - pub generated: BTreeMap>>, - pub association: BTreeMap>>, - pub usage: BTreeMap>>, + pub namespaces: BTreeMap>, + pub agents: BTreeMap>, + pub acted_on_behalf_of: BTreeMap>>, + pub delegation: BTreeMap>>, + pub entities: BTreeMap>, + pub derivation: BTreeMap>>, + pub generation: BTreeMap>>, + pub attribution: BTreeMap>>, + pub activities: BTreeMap>, + pub was_informed_by: BTreeMap>>, + pub generated: BTreeMap>>, + pub association: BTreeMap>>, + pub usage: BTreeMap>>, } -impl MaxEncodedLen for ProvModel { - fn max_encoded_len() -> usize { - 64 * 1024usize - } +// TODO: We can make these structures reasonably bounded (and copy ids with interning) - though JSON +// attributes may need some handwaving +impl parity_scale_codec::MaxEncodedLen for ProvModel { + fn max_encoded_len() -> usize { + 64 * 1024usize + } } impl ProvModel { - /// Merge the supplied ProvModel into this one - pub fn combine(&mut self, other: &ProvModel) { - self.namespaces.extend(other.namespaces.clone()); - self.agents.extend(other.agents.clone()); - self.acted_on_behalf_of - .extend(other.acted_on_behalf_of.clone()); - self.delegation.extend(other.delegation.clone()); - self.entities.extend(other.entities.clone()); - self.derivation.extend(other.derivation.clone()); - self.generation.extend(other.generation.clone()); - self.attribution.extend(other.attribution.clone()); - self.activities.extend(other.activities.clone()); - self.was_informed_by.extend(other.was_informed_by.clone()); - self.generated.extend(other.generated.clone()); - self.association.extend(other.association.clone()); - self.usage.extend(other.usage.clone()); - } - /// Apply a sequence of `ChronicleTransaction` to an empty model, then return it - pub fn from_tx<'a, I>(tx: I) -> Result - where - I: IntoIterator, - { - let mut model = Self::default(); - for tx in tx { - model.apply(tx)?; - } - - Ok(model) - } - - /// Append a derivation to the model - pub fn was_derived_from( - &mut self, - namespace_id: NamespaceId, - typ: DerivationType, - used_id: EntityId, - id: EntityId, - activity_id: Option, - ) { - let derivation_set = Arc::make_mut( - self.derivation - .entry((namespace_id, id.clone())) - .or_default(), - ); - - derivation_set.insert(Derivation { - typ, - generated_id: id, - used_id, - activity_id, - }); - } - - /// Append a delegation to the model - pub fn qualified_delegation( - &mut self, - namespace_id: &NamespaceId, - responsible_id: &AgentId, - delegate_id: &AgentId, - activity_id: Option, - role: Option, - ) { - let delegation = Delegation { - namespace_id: namespace_id.clone(), - id: DelegationId::from_component_ids( - delegate_id, - responsible_id, - activity_id.as_ref(), - role.as_ref(), - ), - responsible_id: responsible_id.clone(), - delegate_id: delegate_id.clone(), - activity_id, - role, - }; - - let delegation_set = Arc::make_mut( - self.delegation - .entry((namespace_id.clone(), responsible_id.clone())) - .or_default(), - ); - delegation_set.insert(delegation.clone()); - - let acted_on_behalf_of_set = Arc::make_mut( - self.acted_on_behalf_of - .entry((namespace_id.clone(), responsible_id.clone())) - .or_default(), - ); - - acted_on_behalf_of_set.insert(delegation); - } - - pub fn qualified_association( - &mut self, - namespace_id: &NamespaceId, - activity_id: &ActivityId, - agent_id: &AgentId, - role: Option, - ) { - let association_set = Arc::make_mut( - self.association - .entry((namespace_id.clone(), activity_id.clone())) - .or_default(), - ); - - association_set.insert(Association { - namespace_id: namespace_id.clone(), - id: AssociationId::from_component_ids(agent_id, activity_id, role.as_ref()), - agent_id: agent_id.clone(), - activity_id: activity_id.clone(), - role, - }); - } - - pub fn was_generated_by( - &mut self, - namespace_id: NamespaceId, - generated_id: &EntityId, - activity_id: &ActivityId, - ) { - let generation_set = Arc::make_mut( - self.generation - .entry((namespace_id.clone(), generated_id.clone())) - .or_default(), - ); - generation_set.insert(Generation { - activity_id: activity_id.clone(), - generated_id: generated_id.clone(), - }); - } - - pub fn generated( - &mut self, - namespace_id: NamespaceId, - generated_id: &ActivityId, - entity_id: &EntityId, - ) { - let generated_set = Arc::make_mut( - self.generated - .entry((namespace_id.clone(), generated_id.clone())) - .or_default(), - ); - - generated_set.insert(GeneratedEntity { - entity_id: entity_id.clone(), - generated_id: generated_id.clone(), - }); - } - - pub fn used( - &mut self, - namespace_id: NamespaceId, - activity_id: &ActivityId, - entity_id: &EntityId, - ) { - let usage_set = Arc::make_mut( - self.usage - .entry((namespace_id.clone(), activity_id.clone())) - .or_default(), - ); - - usage_set.insert(Usage { - activity_id: activity_id.clone(), - entity_id: entity_id.clone(), - }); - } - - pub fn was_informed_by( - &mut self, - namespace_id: NamespaceId, - activity_id: &ActivityId, - informing_activity_id: &ActivityId, - ) { - let was_informed_by_set = Arc::make_mut( - self.was_informed_by - .entry((namespace_id.clone(), activity_id.clone())) - .or_default(), - ); - - was_informed_by_set.insert((namespace_id, informing_activity_id.clone())); - } - - pub fn qualified_attribution( - &mut self, - namespace_id: &NamespaceId, - entity_id: &EntityId, - agent_id: &AgentId, - role: Option, - ) { - let attribution_set = Arc::make_mut( - self.attribution - .entry((namespace_id.clone(), entity_id.clone())) - .or_default(), - ); - - attribution_set.insert(Attribution { - namespace_id: namespace_id.clone(), - id: AttributionId::from_component_ids(agent_id, entity_id, role.as_ref()), - agent_id: agent_id.clone(), - entity_id: entity_id.clone(), - role, - }); - } - - /// Ensure we have the referenced namespace in our model - pub fn namespace_context(&mut self, ns: &NamespaceId) { - let (namespace_name, uuid) = (ns.external_id_part(), ns.uuid_part()); - - self.namespaces.insert( - ns.clone(), - Namespace { - id: ns.clone(), - uuid: uuid.to_owned().into(), - external_id: namespace_name.to_owned(), - } - .into(), - ); - } - - /// Ensure we have the referenced agent in our model, so that open world - /// assumptions can be made - pub fn agent_context(&mut self, ns: &NamespaceId, agent: &AgentId) { - self.agents - .entry((ns.clone(), agent.clone())) - .or_insert_with(|| Agent::exists(ns.clone(), agent.clone()).into()); - } - - pub fn get_agent(&mut self, ns: &NamespaceId, agent: &AgentId) -> Option<&Agent> { - self.agents - .get(&(ns.clone(), agent.clone())) - .map(|arc| arc.as_ref()) - } - pub fn modify_agent( - &mut self, - ns: &NamespaceId, - agent: &AgentId, - f: F, - ) { - if let Some(arc) = self.agents.get_mut(&(ns.clone(), agent.clone())) { - let agent: &mut Agent = Arc::make_mut(arc); - f(agent); - } - } - - /// Ensure we have the referenced entity in our model, so that open world - /// assumptions can be made - pub fn entity_context(&mut self, ns: &NamespaceId, entity: &EntityId) { - self.entities - .entry((ns.clone(), entity.clone())) - .or_insert_with(|| Entity::exists(ns.clone(), entity.clone()).into()); - } - - pub fn get_entity(&mut self, ns: &NamespaceId, entity: &EntityId) -> Option<&Entity> { - self.entities - .get(&(ns.clone(), entity.clone())) - .map(|arc| arc.as_ref()) - } - - pub fn modify_entity( - &mut self, - ns: &NamespaceId, - entity: &EntityId, - f: F, - ) { - if let Some(arc) = self.entities.get_mut(&(ns.clone(), entity.clone())) { - let entity: &mut Entity = Arc::make_mut(arc); - f(entity); - } - } - - /// Ensure we have the referenced activity in our model, so that open world - /// assumptions can be made - pub fn activity_context(&mut self, ns: &NamespaceId, activity: &ActivityId) { - self.activities - .entry((ns.clone(), activity.clone())) - .or_insert_with(|| Activity::exists(ns.clone(), activity.clone()).into()); - } - - pub fn get_activity(&mut self, ns: &NamespaceId, activity: &ActivityId) -> Option<&Activity> { - self.activities - .get(&(ns.clone(), activity.clone())) - .map(|arc| arc.as_ref()) - } - - pub fn modify_activity( - &mut self, - ns: &NamespaceId, - activity: &ActivityId, - f: F, - ) { - if let Some(arc) = self.activities.get_mut(&(ns.clone(), activity.clone())) { - let activity: &mut Activity = Arc::make_mut(arc); - f(activity); - } - } - - /// Transform a sequence of `ChronicleOperation` events into a provenance model, - /// If a statement requires a subject or object that does not currently exist in the model, then we create it - /// If an operation contradicts a previous statement, then we record the - /// contradiction, but attempt to apply as much of the operation as possible - #[instrument(skip(self,tx), level = "trace", name="apply_chronicle_operation", fields(op = ?tx, model= ?self), ret(Debug))] - pub fn apply(&mut self, tx: &ChronicleOperation) -> Result<(), Contradiction> { - let tx = tx.to_owned(); - match tx { - ChronicleOperation::CreateNamespace(CreateNamespace { - id, - external_id: _, - uuid: _, - }) => { - self.namespace_context(&id); - Ok(()) - } - ChronicleOperation::AgentExists(AgentExists { - namespace, - external_id, - .. - }) => { - self.namespace_context(&namespace); - self.agent_context(&namespace, &AgentId::from_external_id(&external_id)); - - Ok(()) - } - ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf { - id: _, - namespace, - delegate_id, - activity_id, - role, - responsible_id, - }) => { - self.namespace_context(&namespace); - self.agent_context(&namespace, &delegate_id); - self.agent_context(&namespace, &responsible_id); - - if let Some(activity_id) = activity_id.clone() { - self.activity_context(&namespace, &activity_id); - } - - self.qualified_delegation( - &namespace, - &responsible_id, - &delegate_id, - activity_id, - role, - ); - - Ok(()) - } - ChronicleOperation::ActivityExists(ActivityExists { - namespace, - external_id, - .. - }) => { - self.namespace_context(&namespace); - self.activity_context(&namespace, &ActivityId::from_external_id(&external_id)); - - Ok(()) - } - ChronicleOperation::StartActivity(StartActivity { - namespace, - id, - time, - }) => { - self.namespace_context(&namespace); - self.activity_context(&namespace, &id); - - let activity = self.get_activity(&namespace, &id); - - trace!(check_start_contradiction = ?time, existing_time=?activity.and_then(|activity| activity.started)); - match ( - activity.and_then(|activity| activity.started), - activity.and_then(|activity| activity.ended), - ) { - (Some(TimeWrapper(started)), _) if started != time.0 => { - return Err(Contradiction::start_date_alteration( - id.into(), - namespace, - started, - time.0, - )); - } - (_, Some(TimeWrapper(ended))) if ended < time.0 => { - return Err(Contradiction::invalid_range( - id.into(), - namespace, - time.0, - ended, - )); - } - _ => {} - }; - - self.modify_activity(&namespace, &id, move |activity| { - activity.started = Some(time); - }); - - Ok(()) - } - ChronicleOperation::EndActivity(EndActivity { - namespace, - id, - time, - }) => { - self.namespace_context(&namespace); - self.activity_context(&namespace, &id); - - let activity = self.get_activity(&namespace, &id); - - trace!(check_end_contradiction = ?time, existing_time=?activity.and_then(|activity| activity.ended)); - match ( - activity.and_then(|activity| activity.started), - activity.and_then(|activity| activity.ended), - ) { - (_, Some(TimeWrapper(ended))) if ended != time.0 => { - return Err(Contradiction::end_date_alteration( - id.into(), - namespace, - ended, - time.0, - )); - } - (Some(TimeWrapper(started)), _) if started > time.0 => { - return Err(Contradiction::invalid_range( - id.into(), - namespace, - started, - time.0, - )); - } - _ => {} - }; - - self.modify_activity(&namespace, &id, move |activity| { - activity.ended = Some(time); - }); - - Ok(()) - } - ChronicleOperation::WasAssociatedWith(WasAssociatedWith { - id: _, - role, - namespace, - activity_id, - agent_id, - }) => { - self.namespace_context(&namespace); - self.agent_context(&namespace, &agent_id); - self.activity_context(&namespace, &activity_id); - self.qualified_association(&namespace, &activity_id, &agent_id, role); - - Ok(()) - } - ChronicleOperation::WasAttributedTo(WasAttributedTo { - id: _, - role, - namespace, - entity_id, - agent_id, - }) => { - self.namespace_context(&namespace); - self.agent_context(&namespace, &agent_id); - self.entity_context(&namespace, &entity_id); - self.qualified_attribution(&namespace, &entity_id, &agent_id, role); - - Ok(()) - } - ChronicleOperation::ActivityUses(ActivityUses { - namespace, - id, - activity, - }) => { - self.namespace_context(&namespace); - - self.activity_context(&namespace, &activity); - self.entity_context(&namespace, &id); - - self.used(namespace, &activity, &id); - - Ok(()) - } - ChronicleOperation::EntityExists(EntityExists { - namespace, - external_id, - .. - }) => { - self.namespace_context(&namespace); - self.entity_context(&namespace, &EntityId::from_external_id(&external_id)); - Ok(()) - } - ChronicleOperation::WasGeneratedBy(WasGeneratedBy { - namespace, - id, - activity, - }) => { - self.namespace_context(&namespace); - - self.entity_context(&namespace, &id); - self.activity_context(&namespace, &activity); - - self.was_generated_by(namespace, &id, &activity); - - Ok(()) - } - ChronicleOperation::WasInformedBy(WasInformedBy { - namespace, - activity, - informing_activity, - }) => { - self.namespace_context(&namespace); - self.activity_context(&namespace, &activity); - self.activity_context(&namespace, &informing_activity); - - self.was_informed_by(namespace, &activity, &informing_activity); - - Ok(()) - } - ChronicleOperation::EntityDerive(EntityDerive { - namespace, - id, - typ, - used_id, - activity_id, - }) => { - self.namespace_context(&namespace); - - self.entity_context(&namespace, &id); - self.entity_context(&namespace, &used_id); - - if let Some(activity_id) = &activity_id { - self.activity_context(&namespace, activity_id); - } - - self.was_derived_from(namespace, typ, used_id, id, activity_id); - - Ok(()) - } - ChronicleOperation::SetAttributes(SetAttributes::Entity { - namespace, - id, - attributes, - }) => { - self.namespace_context(&namespace); - self.entity_context(&namespace, &id); - - if let Some(current) = self - .entities - .get(&(namespace.clone(), id.clone())) - .map(|entity| &entity.attributes) - { - Self::validate_attribute_changes( - &id.clone().into(), - &namespace, - current, - &attributes, - )?; - }; - - self.modify_entity(&namespace, &id, move |entity| { - entity.domaintypeid = attributes.typ.clone(); - entity.attributes = attributes.attributes; - }); - - Ok(()) - } - ChronicleOperation::SetAttributes(SetAttributes::Activity { - namespace, - id, - attributes, - }) => { - self.namespace_context(&namespace); - self.activity_context(&namespace, &id); - - if let Some(current) = self - .activities - .get(&(namespace.clone(), id.clone())) - .map(|activity| &activity.attributes) - { - Self::validate_attribute_changes( - &id.clone().into(), - &namespace, - current, - &attributes, - )?; - }; - - self.modify_activity(&namespace, &id, move |activity| { - activity.domaintypeid = attributes.typ.clone(); - activity.attributes = attributes.attributes; - }); - - Ok(()) - } - ChronicleOperation::SetAttributes(SetAttributes::Agent { - namespace, - id, - attributes, - }) => { - self.namespace_context(&namespace); - self.agent_context(&namespace, &id); - - if let Some(current) = self - .agents - .get(&(namespace.clone(), id.clone())) - .map(|agent| &agent.attributes) - { - Self::validate_attribute_changes( - &id.clone().into(), - &namespace, - current, - &attributes, - )?; - }; - - self.modify_agent(&namespace, &id, move |agent| { - agent.domaintypeid = attributes.typ.clone(); - agent.attributes = attributes.attributes; - }); - - Ok(()) - } - } - } - - /// Allow additional attributes, but changing an existing attribute is not allowed - #[instrument(level = "trace", ret(Debug))] - fn validate_attribute_changes( - id: &ChronicleIri, - namespace: &NamespaceId, - current: &BTreeMap, - attempted: &Attributes, - ) -> Result<(), Contradiction> { - let contradictions = attempted - .attributes - .iter() - .filter_map(|(current_name, current_value)| { - if let Some(attempted_value) = current.get(current_name) { - if current_value != attempted_value { - Some(( - current_name.clone(), - current_value.clone(), - attempted_value.clone(), - )) - } else { - None - } - } else { - None - } - }) - .collect::>(); - - if contradictions.is_empty() { - Ok(()) - } else { - Err(Contradiction::attribute_value_change( - id.clone(), - namespace.clone(), - contradictions, - )) - } - } - - pub(crate) fn add_agent(&mut self, agent: Agent) { - self.agents - .insert((agent.namespaceid.clone(), agent.id.clone()), agent.into()); - } - - pub(crate) fn add_activity(&mut self, activity: Activity) { - self.activities.insert( - (activity.namespaceid.clone(), activity.id.clone()), - activity.into(), - ); - } - - pub(crate) fn add_entity(&mut self, entity: Entity) { - self.entities.insert( - (entity.namespaceid.clone(), entity.id.clone()), - entity.into(), - ); - } -} - -custom_error::custom_error! {pub CompactionError - JsonLd{inner: String} = "JSON-LD: {inner}", //TODO: contribute Send to the upstream JsonLD error type - Join{source : JoinError} = "Tokio: {source}", - Serde{source: serde_json::Error} = "Serde conversion: {source}", - InvalidExpanded{message: String} = "Expanded document invalid: {message}", - NoObject{document: Value} = "Compacted document not a JSON object: {document}", + /// Merge the supplied ProvModel into this one + pub fn combine(&mut self, other: &ProvModel) { + self.namespaces.extend(other.namespaces.clone()); + self.agents.extend(other.agents.clone()); + self.acted_on_behalf_of.extend(other.acted_on_behalf_of.clone()); + self.delegation.extend(other.delegation.clone()); + self.entities.extend(other.entities.clone()); + self.derivation.extend(other.derivation.clone()); + self.generation.extend(other.generation.clone()); + self.attribution.extend(other.attribution.clone()); + self.activities.extend(other.activities.clone()); + self.was_informed_by.extend(other.was_informed_by.clone()); + self.generated.extend(other.generated.clone()); + self.association.extend(other.association.clone()); + self.usage.extend(other.usage.clone()); + } + /// Apply a sequence of `ChronicleTransaction` to an empty model, then return it + pub fn from_tx<'a, I>(tx: I) -> Result + where + I: IntoIterator, + { + let mut model = Self::default(); + for tx in tx { + model.apply(tx)?; + } + + Ok(model) + } + + /// Append a derivation to the model + pub fn was_derived_from( + &mut self, + namespace_id: NamespaceId, + typ: DerivationType, + used_id: EntityId, + id: EntityId, + activity_id: Option, + ) { + let derivation_set = + Arc::make_mut(self.derivation.entry((namespace_id, id.clone())).or_default()); + + derivation_set.insert(Derivation { typ, generated_id: id, used_id, activity_id }); + } + + /// Append a delegation to the model + pub fn qualified_delegation( + &mut self, + namespace_id: &NamespaceId, + responsible_id: &AgentId, + delegate_id: &AgentId, + activity_id: Option, + role: Option, + ) { + let delegation = Delegation { + namespace_id: namespace_id.clone(), + id: DelegationId::from_component_ids( + delegate_id, + responsible_id, + activity_id.as_ref(), + role.as_ref(), + ), + responsible_id: responsible_id.clone(), + delegate_id: delegate_id.clone(), + activity_id, + role, + }; + + let delegation_set = Arc::make_mut( + self.delegation + .entry((namespace_id.clone(), responsible_id.clone())) + .or_default(), + ); + delegation_set.insert(delegation.clone()); + + let acted_on_behalf_of_set = Arc::make_mut( + self.acted_on_behalf_of + .entry((namespace_id.clone(), responsible_id.clone())) + .or_default(), + ); + + acted_on_behalf_of_set.insert(delegation); + } + + pub fn qualified_association( + &mut self, + namespace_id: &NamespaceId, + activity_id: &ActivityId, + agent_id: &AgentId, + role: Option, + ) { + let association_set = Arc::make_mut( + self.association.entry((namespace_id.clone(), activity_id.clone())).or_default(), + ); + + association_set.insert(Association { + namespace_id: namespace_id.clone(), + id: AssociationId::from_component_ids(agent_id, activity_id, role.as_ref()), + agent_id: agent_id.clone(), + activity_id: activity_id.clone(), + role, + }); + } + + pub fn was_generated_by( + &mut self, + namespace_id: NamespaceId, + generated_id: &EntityId, + activity_id: &ActivityId, + ) { + let generation_set = Arc::make_mut( + self.generation.entry((namespace_id.clone(), generated_id.clone())).or_default(), + ); + generation_set.insert(Generation { + activity_id: activity_id.clone(), + generated_id: generated_id.clone(), + }); + } + + pub fn generated( + &mut self, + namespace_id: NamespaceId, + generated_id: &ActivityId, + entity_id: &EntityId, + ) { + let generated_set = Arc::make_mut( + self.generated.entry((namespace_id.clone(), generated_id.clone())).or_default(), + ); + + generated_set.insert(GeneratedEntity { + entity_id: entity_id.clone(), + generated_id: generated_id.clone(), + }); + } + + pub fn used( + &mut self, + namespace_id: NamespaceId, + activity_id: &ActivityId, + entity_id: &EntityId, + ) { + let usage_set = Arc::make_mut( + self.usage.entry((namespace_id.clone(), activity_id.clone())).or_default(), + ); + + usage_set.insert(Usage { activity_id: activity_id.clone(), entity_id: entity_id.clone() }); + } + + pub fn was_informed_by( + &mut self, + namespace_id: NamespaceId, + activity_id: &ActivityId, + informing_activity_id: &ActivityId, + ) { + let was_informed_by_set = Arc::make_mut( + self.was_informed_by + .entry((namespace_id.clone(), activity_id.clone())) + .or_default(), + ); + + was_informed_by_set.insert((namespace_id, informing_activity_id.clone())); + } + + pub fn qualified_attribution( + &mut self, + namespace_id: &NamespaceId, + entity_id: &EntityId, + agent_id: &AgentId, + role: Option, + ) { + let attribution_set = Arc::make_mut( + self.attribution.entry((namespace_id.clone(), entity_id.clone())).or_default(), + ); + + attribution_set.insert(Attribution { + namespace_id: namespace_id.clone(), + id: AttributionId::from_component_ids(agent_id, entity_id, role.as_ref()), + agent_id: agent_id.clone(), + entity_id: entity_id.clone(), + role, + }); + } + + /// Ensure we have the referenced namespace in our model + pub fn namespace_context(&mut self, ns: &NamespaceId) { + let (namespace_name, uuid) = (ns.external_id_part(), ns.uuid_part()); + + self.namespaces.insert( + ns.clone(), + Namespace { + id: ns.clone(), + uuid: uuid.to_owned().into(), + external_id: namespace_name.to_owned(), + } + .into(), + ); + } + + /// Ensure we have the referenced agent in our model, so that open world + /// assumptions can be made + pub fn agent_context(&mut self, ns: &NamespaceId, agent: &AgentId) { + self.agents + .entry((ns.clone(), agent.clone())) + .or_insert_with(|| Agent::exists(ns.clone(), agent.clone()).into()); + } + + pub fn get_agent(&mut self, ns: &NamespaceId, agent: &AgentId) -> Option<&Agent> { + self.agents.get(&(ns.clone(), agent.clone())).map(|arc| arc.as_ref()) + } + pub fn modify_agent( + &mut self, + ns: &NamespaceId, + agent: &AgentId, + f: F, + ) { + if let Some(arc) = self.agents.get_mut(&(ns.clone(), agent.clone())) { + let agent: &mut Agent = Arc::make_mut(arc); + f(agent); + } + } + + /// Ensure we have the referenced entity in our model, so that open world + /// assumptions can be made + pub fn entity_context(&mut self, ns: &NamespaceId, entity: &EntityId) { + self.entities + .entry((ns.clone(), entity.clone())) + .or_insert_with(|| Entity::exists(ns.clone(), entity.clone()).into()); + } + + pub fn get_entity(&mut self, ns: &NamespaceId, entity: &EntityId) -> Option<&Entity> { + self.entities.get(&(ns.clone(), entity.clone())).map(|arc| arc.as_ref()) + } + + pub fn modify_entity( + &mut self, + ns: &NamespaceId, + entity: &EntityId, + f: F, + ) { + if let Some(arc) = self.entities.get_mut(&(ns.clone(), entity.clone())) { + let entity: &mut Entity = Arc::make_mut(arc); + f(entity); + } + } + + /// Ensure we have the referenced activity in our model, so that open world + /// assumptions can be made + pub fn activity_context(&mut self, ns: &NamespaceId, activity: &ActivityId) { + self.activities + .entry((ns.clone(), activity.clone())) + .or_insert_with(|| Activity::exists(ns.clone(), activity.clone()).into()); + } + + pub fn get_activity(&mut self, ns: &NamespaceId, activity: &ActivityId) -> Option<&Activity> { + self.activities.get(&(ns.clone(), activity.clone())).map(|arc| arc.as_ref()) + } + + pub fn modify_activity( + &mut self, + ns: &NamespaceId, + activity: &ActivityId, + f: F, + ) { + if let Some(arc) = self.activities.get_mut(&(ns.clone(), activity.clone())) { + let activity: &mut Activity = Arc::make_mut(arc); + f(activity); + } + } + + /// Transform a sequence of `ChronicleOperation` events into a provenance model, + /// If a statement requires a subject or object that does not currently exist in the model, then + /// we create it If an operation contradicts a previous statement, then we record the + /// contradiction, but attempt to apply as much of the operation as possible + #[instrument(skip(self,tx), level = "trace", name="apply_chronicle_operation", fields(op = ?tx, model= ?self), ret(Debug))] + pub fn apply(&mut self, tx: &ChronicleOperation) -> Result<(), Contradiction> { + let tx = tx.to_owned(); + match tx { + ChronicleOperation::CreateNamespace(CreateNamespace { + id, + external_id: _, + uuid: _, + }) => { + self.namespace_context(&id); + Ok(()) + }, + ChronicleOperation::AgentExists(AgentExists { namespace, external_id, .. }) => { + self.namespace_context(&namespace); + self.agent_context(&namespace, &AgentId::from_external_id(&external_id)); + + Ok(()) + }, + ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf { + id: _, + namespace, + delegate_id, + activity_id, + role, + responsible_id, + }) => { + self.namespace_context(&namespace); + self.agent_context(&namespace, &delegate_id); + self.agent_context(&namespace, &responsible_id); + + if let Some(activity_id) = activity_id.clone() { + self.activity_context(&namespace, &activity_id); + } + + self.qualified_delegation( + &namespace, + &responsible_id, + &delegate_id, + activity_id, + role, + ); + + Ok(()) + }, + ChronicleOperation::ActivityExists(ActivityExists { + namespace, external_id, .. + }) => { + self.namespace_context(&namespace); + self.activity_context(&namespace, &ActivityId::from_external_id(&external_id)); + + Ok(()) + }, + ChronicleOperation::StartActivity(StartActivity { namespace, id, time }) => { + self.namespace_context(&namespace); + self.activity_context(&namespace, &id); + + let activity = self.get_activity(&namespace, &id); + + trace!(check_start_contradiction = ?time, existing_time=?activity.and_then(|activity| activity.started)); + match ( + activity.and_then(|activity| activity.started), + activity.and_then(|activity| activity.ended), + ) { + (Some(TimeWrapper(started)), _) if started != time.0 => { + return Err(Contradiction::start_date_alteration( + id.into(), + namespace, + started, + time.0, + )) + }, + (_, Some(TimeWrapper(ended))) if ended < time.0 => { + return Err(Contradiction::invalid_range( + id.into(), + namespace, + time.0, + ended, + )) + }, + _ => {}, + }; + + self.modify_activity(&namespace, &id, move |activity| { + activity.started = Some(time); + }); + + Ok(()) + }, + ChronicleOperation::EndActivity(EndActivity { namespace, id, time }) => { + self.namespace_context(&namespace); + self.activity_context(&namespace, &id); + + let activity = self.get_activity(&namespace, &id); + + trace!(check_end_contradiction = ?time, existing_time=?activity.and_then(|activity| activity.ended)); + match ( + activity.and_then(|activity| activity.started), + activity.and_then(|activity| activity.ended), + ) { + (_, Some(TimeWrapper(ended))) if ended != time.0 => { + return Err(Contradiction::end_date_alteration( + id.into(), + namespace, + ended, + time.0, + )) + }, + (Some(TimeWrapper(started)), _) if started > time.0 => { + return Err(Contradiction::invalid_range( + id.into(), + namespace, + started, + time.0, + )) + }, + _ => {}, + }; + + self.modify_activity(&namespace, &id, move |activity| { + activity.ended = Some(time); + }); + + Ok(()) + }, + ChronicleOperation::WasAssociatedWith(WasAssociatedWith { + id: _, + role, + namespace, + activity_id, + agent_id, + }) => { + self.namespace_context(&namespace); + self.agent_context(&namespace, &agent_id); + self.activity_context(&namespace, &activity_id); + self.qualified_association(&namespace, &activity_id, &agent_id, role); + + Ok(()) + }, + ChronicleOperation::WasAttributedTo(WasAttributedTo { + id: _, + role, + namespace, + entity_id, + agent_id, + }) => { + self.namespace_context(&namespace); + self.agent_context(&namespace, &agent_id); + self.entity_context(&namespace, &entity_id); + self.qualified_attribution(&namespace, &entity_id, &agent_id, role); + + Ok(()) + }, + ChronicleOperation::ActivityUses(ActivityUses { namespace, id, activity }) => { + self.namespace_context(&namespace); + + self.activity_context(&namespace, &activity); + self.entity_context(&namespace, &id); + + self.used(namespace, &activity, &id); + + Ok(()) + }, + ChronicleOperation::EntityExists(EntityExists { namespace, external_id, .. }) => { + self.namespace_context(&namespace); + self.entity_context(&namespace, &EntityId::from_external_id(&external_id)); + Ok(()) + }, + ChronicleOperation::WasGeneratedBy(WasGeneratedBy { namespace, id, activity }) => { + self.namespace_context(&namespace); + + self.entity_context(&namespace, &id); + self.activity_context(&namespace, &activity); + + self.was_generated_by(namespace, &id, &activity); + + Ok(()) + }, + ChronicleOperation::WasInformedBy(WasInformedBy { + namespace, + activity, + informing_activity, + }) => { + self.namespace_context(&namespace); + self.activity_context(&namespace, &activity); + self.activity_context(&namespace, &informing_activity); + + self.was_informed_by(namespace, &activity, &informing_activity); + + Ok(()) + }, + ChronicleOperation::EntityDerive(EntityDerive { + namespace, + id, + typ, + used_id, + activity_id, + }) => { + self.namespace_context(&namespace); + + self.entity_context(&namespace, &id); + self.entity_context(&namespace, &used_id); + + if let Some(activity_id) = &activity_id { + self.activity_context(&namespace, activity_id); + } + + self.was_derived_from(namespace, typ, used_id, id, activity_id); + + Ok(()) + }, + ChronicleOperation::SetAttributes(SetAttributes::Entity { + namespace, + id, + attributes, + }) => { + self.namespace_context(&namespace); + self.entity_context(&namespace, &id); + + if let Some(current) = self + .entities + .get(&(namespace.clone(), id.clone())) + .map(|entity| &entity.attributes) + { + Self::validate_attribute_changes( + &id.clone().into(), + &namespace, + current, + &attributes, + )?; + }; + + self.modify_entity(&namespace, &id, move |entity| { + entity.domaintypeid = attributes.typ.clone(); + entity.attributes = attributes.attributes; + }); + + Ok(()) + }, + ChronicleOperation::SetAttributes(SetAttributes::Activity { + namespace, + id, + attributes, + }) => { + self.namespace_context(&namespace); + self.activity_context(&namespace, &id); + + if let Some(current) = self + .activities + .get(&(namespace.clone(), id.clone())) + .map(|activity| &activity.attributes) + { + Self::validate_attribute_changes( + &id.clone().into(), + &namespace, + current, + &attributes, + )?; + }; + + self.modify_activity(&namespace, &id, move |activity| { + activity.domaintypeid = attributes.typ.clone(); + activity.attributes = attributes.attributes; + }); + + Ok(()) + }, + ChronicleOperation::SetAttributes(SetAttributes::Agent { + namespace, + id, + attributes, + }) => { + self.namespace_context(&namespace); + self.agent_context(&namespace, &id); + + if let Some(current) = + self.agents.get(&(namespace.clone(), id.clone())).map(|agent| &agent.attributes) + { + Self::validate_attribute_changes( + &id.clone().into(), + &namespace, + current, + &attributes, + )?; + }; + + self.modify_agent(&namespace, &id, move |agent| { + agent.domaintypeid = attributes.typ.clone(); + agent.attributes = attributes.attributes; + }); + + Ok(()) + }, + } + } + + /// Allow additional attributes, but changing an existing attribute is not allowed + #[instrument(level = "trace", ret(Debug))] + fn validate_attribute_changes( + id: &ChronicleIri, + namespace: &NamespaceId, + current: &BTreeMap, + attempted: &Attributes, + ) -> Result<(), Contradiction> { + let contradictions = attempted + .attributes + .iter() + .filter_map(|(current_name, current_value)| { + if let Some(attempted_value) = current.get(current_name) { + if current_value != attempted_value { + Some((current_name.clone(), current_value.clone(), attempted_value.clone())) + } else { + None + } + } else { + None + } + }) + .collect::>(); + + if contradictions.is_empty() { + Ok(()) + } else { + Err(Contradiction::attribute_value_change( + id.clone(), + namespace.clone(), + contradictions, + )) + } + } + + pub(crate) fn add_agent(&mut self, agent: Agent) { + self.agents.insert((agent.namespaceid.clone(), agent.id.clone()), agent.into()); + } + + pub(crate) fn add_activity(&mut self, activity: Activity) { + self.activities + .insert((activity.namespaceid.clone(), activity.id.clone()), activity.into()); + } + + pub(crate) fn add_entity(&mut self, entity: Entity) { + self.entities + .insert((entity.namespaceid.clone(), entity.id.clone()), entity.into()); + } } -pub struct ExpandedJson(pub serde_json::Value); - -fn construct_context_definition( - json: &serde_json::Value, - metadata: M, -) -> json_ld::syntax::context::Definition -where - M: Clone + Debug, -{ - use json_ld::syntax::{ - context::{ - definition::{Bindings, Version}, - Definition, TermDefinition, - }, - Entry, Nullable, TryFromJson, - }; - if let Value::Object(map) = json { - match map.get("@version") { - None => {} - Some(Value::Number(version)) if version.as_f64() == Some(1.1) => {} - Some(json_version) => panic!("unexpected JSON-LD context @version: {json_version}"), - }; - let mut bindings = Bindings::new(); - for (key, value) in map { - if key == "@version" { - // already handled above - } else if let Some('@') = key.chars().next() { - panic!("unexpected JSON-LD context key: {key}"); - } else { - let value = - json_ld::syntax::Value::from_serde_json(value.clone(), |_| metadata.clone()); - let term: Meta, M> = TryFromJson::try_from_json(value) - .expect("failed to convert {value} to term binding"); - bindings.insert( - Meta(key.clone().into(), metadata.clone()), - Meta(Nullable::Some(term.value().clone()), metadata.clone()), - ); - } - } - Definition { - base: None, - import: None, - language: None, - direction: None, - propagate: None, - protected: None, - type_: None, - version: Some(Entry::new( - metadata.clone(), - Meta::new(Version::V1_1, metadata), - )), - vocab: None, - bindings, - } - } else { - panic!("failed to convert JSON to LD context: {json:?}"); - } -} - -lazy_static! { - static ref JSON_LD_CONTEXT_DEFS: json_ld::syntax::context::Definition<()> = - construct_context_definition(&crate::context::PROV, ()); -} - -impl ExpandedJson { - async fn compact_unordered(self) -> Result { - use json_ld::{ - syntax::context, Compact, ExpandedDocument, Process, ProcessingMode, TryFromJson, - }; - - let vocabulary = no_vocabulary_mut(); - let mut loader: NoLoader = NoLoader::new(); - - // process context - let value = context::Value::One(Meta::new( - context::Context::Definition(JSON_LD_CONTEXT_DEFS.clone()), - (), - )); - let context_meta = Meta::new(value, ()); - let processed_context = context_meta - .process(vocabulary, &mut loader, None) - .await - .map_err(|e| CompactionError::JsonLd { - inner: format!("{:?}", e), - })?; - - // compact document - - let expanded_meta = json_ld::syntax::Value::from_serde_json(self.0, |_| ()); - - let expanded_doc: Meta, ()> = - TryFromJson::try_from_json_in(vocabulary, expanded_meta).map_err(|e| { - CompactionError::InvalidExpanded { - message: format!("{:?}", e.into_value()), - } - })?; - - let output = expanded_doc - .compact_full( - vocabulary, - processed_context.as_ref(), - &mut loader, - json_ld::compaction::Options { - processing_mode: ProcessingMode::JsonLd1_1, - compact_to_relative: true, - compact_arrays: true, - ordered: true, - }, - ) - .await - .map_err(|e| CompactionError::JsonLd { - inner: e.to_string(), - })?; - - // Sort @graph - - // reference context - - let json: Value = output.into_value().into(); - - if let Value::Object(mut map) = json { - map.insert( - "@context".to_string(), - Value::String("https://btp.works/chr/1.0/c.jsonld".to_string()), - ); - Ok(CompactedJson(Value::Object(map))) - } else { - Err(CompactionError::NoObject { document: json }) - } - } - - // Sort @graph by json value, as they are unstable and we need deterministic output - pub async fn compact(self) -> Result { - let mut v: serde_json::Value = - serde_json::from_str(&self.compact_unordered().await?.0.to_string())?; - - if let Some(v) = v.pointer_mut("/@graph").and_then(|p| p.as_array_mut()) { - v.sort_by_cached_key(|v| v.to_string()); - } - - Ok(v) - } - - pub async fn compact_stable_order(self) -> Result { - self.compact().await - } -} -pub mod from_json_ld; - -pub struct CompactedJson(pub serde_json::Value); - -impl std::ops::Deref for CompactedJson { - type Target = serde_json::Value; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl CompactedJson { - pub fn pretty(&self) -> String { - serde_json::to_string_pretty(&self.0).unwrap() - } -} - -/// Property testing of prov models created and round tripped via JSON / LD -#[cfg(test)] -pub mod proptest; diff --git a/crates/common/src/prov/model/proptest.rs b/crates/common/src/prov/model/proptest.rs index 7a74d8be6..7d9d50459 100644 --- a/crates/common/src/prov/model/proptest.rs +++ b/crates/common/src/prov/model/proptest.rs @@ -4,616 +4,609 @@ use proptest::{option, prelude::*}; use uuid::Uuid; use crate::{ - attributes::{Attribute, Attributes}, - prov::{ - operations::*, to_json_ld::ToJson, ActivityId, AgentId, Association, AssociationId, - Attribution, Contradiction, Delegation, DelegationId, Derivation, DomaintypeId, EntityId, - ExternalId, ExternalIdPart, Generation, NamespaceId, ProvModel, Role, Usage, UuidPart, - }, + attributes::{Attribute, Attributes}, + prov::{ + operations::*, to_json_ld::ToJson, ActivityId, AgentId, Association, AssociationId, + Attribution, Contradiction, Delegation, DelegationId, Derivation, DomaintypeId, EntityId, + ExternalId, ExternalIdPart, Generation, NamespaceId, ProvModel, Role, Usage, UuidPart, + }, }; use super::{ActivityUses, ActsOnBehalfOf, EntityDerive, StartActivity}; prop_compose! { - fn an_external_id()(external_id in "[A-Za-z]") -> ExternalId { - ExternalId::from(external_id) - } + fn an_external_id()(external_id in "[A-Za-z]") -> ExternalId { + ExternalId::from(external_id) + } } prop_compose! { - fn a_symbol()(external_id in "[A-Za-z]") -> String { - external_id - } + fn a_symbol()(external_id in "[A-Za-z]") -> String { + external_id + } } // Choose from a limited selection of types so that we get multiple references prop_compose! { - fn typ()(names in prop::collection::vec(a_symbol(), 5), index in (0..5usize)) -> String { - names.get(index).unwrap().to_owned() - } + fn typ()(names in prop::collection::vec(a_symbol(), 5), index in (0..5usize)) -> String { + names.get(index).unwrap().to_owned() + } } // Choose from a limited selection of names so that we get multiple references prop_compose! { - fn external_id()(external_ids in prop::collection::vec(an_external_id(), 5), index in (0..5usize)) -> ExternalId { - external_ids.get(index).unwrap().to_owned() - } + fn external_id()(external_ids in prop::collection::vec(an_external_id(), 5), index in (0..5usize)) -> ExternalId { + external_ids.get(index).unwrap().to_owned() + } } // Choose from a limited selection of domain types prop_compose! { - fn domain_type_id()(names in prop::collection::vec(a_symbol(), 5), index in (0..5usize)) -> DomaintypeId { - DomaintypeId::from_external_id(&ExternalId::from(names.get(index).unwrap())) - } + fn domain_type_id()(names in prop::collection::vec(a_symbol(), 5), index in (0..5usize)) -> DomaintypeId { + DomaintypeId::from_external_id(&ExternalId::from(names.get(index).unwrap())) + } } prop_compose! { - fn a_namespace() - (uuid in prop::collection::vec(0..255u8, 16), - external_id in external_id()) -> NamespaceId { + fn a_namespace() + (uuid in prop::collection::vec(0..255u8, 16), + external_id in external_id()) -> NamespaceId { - NamespaceId::from_external_id(&external_id,Uuid::from_bytes(uuid.as_slice().try_into().unwrap())) - } + NamespaceId::from_external_id(&external_id,Uuid::from_bytes(uuid.as_slice().try_into().unwrap())) + } } // Choose from a limited selection of namespaces so that we get multiple references prop_compose! { - fn namespace()(namespaces in prop::collection::vec(a_namespace(), 1), index in (0..1usize)) -> NamespaceId { - namespaces.get(index).unwrap().to_owned() - } + fn namespace()(namespaces in prop::collection::vec(a_namespace(), 1), index in (0..1usize)) -> NamespaceId { + namespaces.get(index).unwrap().to_owned() + } } prop_compose! { - fn create_namespace()(id in namespace()) -> CreateNamespace { - let (external_id,uuid) = (id.external_id_part(), id.uuid_part()); - CreateNamespace { - id: id.clone(), - uuid: (*uuid).into(), - external_id: external_id.to_owned(), - } - } + fn create_namespace()(id in namespace()) -> CreateNamespace { + let (external_id,uuid) = (id.external_id_part(), id.uuid_part()); + CreateNamespace { + id: id.clone(), + uuid: (*uuid).into(), + external_id: external_id.to_owned(), + } + } } prop_compose! { - fn create_agent() (external_id in external_id(),namespace in namespace()) -> AgentExists { - let _id = AgentId::from_external_id(&external_id); - AgentExists { - namespace, - external_id, - } - } + fn create_agent() (external_id in external_id(),namespace in namespace()) -> AgentExists { + let _id = AgentId::from_external_id(&external_id); + AgentExists { + namespace, + external_id, + } + } } prop_compose! { - fn create_activity() (external_id in external_id(),namespace in namespace()) -> ActivityExists { - ActivityExists { - namespace, - external_id, - } - } + fn create_activity() (external_id in external_id(),namespace in namespace()) -> ActivityExists { + ActivityExists { + namespace, + external_id, + } + } } // Create times for start between 2-1 years in the past, to ensure start <= end prop_compose! { - fn start_activity() (external_id in external_id(),namespace in namespace(), offset in (0..10)) -> StartActivity { - let id = ActivityId::from_external_id(&external_id); + fn start_activity() (external_id in external_id(),namespace in namespace(), offset in (0..10)) -> StartActivity { + let id = ActivityId::from_external_id(&external_id); - let today = Utc::now().date_naive().and_hms_micro_opt(0, 0, 0, 0).unwrap().and_local_timezone(Utc).unwrap(); + let today = Utc::now().date_naive().and_hms_micro_opt(0, 0, 0, 0).unwrap().and_local_timezone(Utc).unwrap(); - StartActivity { - namespace, - id, - time: (today - chrono::Duration::days(offset as _)).into() - } - } + StartActivity { + namespace, + id, + time: (today - chrono::Duration::days(offset as _)).into() + } + } } // Create times for start between 2-1 years in the past, to ensure start <= end prop_compose! { - fn end_activity() (external_id in external_id(),namespace in namespace(), offset in (0..10)) -> EndActivity { - let id = ActivityId::from_external_id(&external_id); + fn end_activity() (external_id in external_id(),namespace in namespace(), offset in (0..10)) -> EndActivity { + let id = ActivityId::from_external_id(&external_id); - let today = Utc::now().date_naive().and_hms_micro_opt(0, 0, 0, 0).unwrap().and_local_timezone(Utc).unwrap(); + let today = Utc::now().date_naive().and_hms_micro_opt(0, 0, 0, 0).unwrap().and_local_timezone(Utc).unwrap(); - EndActivity { - namespace, - id, - time: (today - chrono::Duration::days(offset as _)).into() - } - } + EndActivity { + namespace, + id, + time: (today - chrono::Duration::days(offset as _)).into() + } + } } prop_compose! { - fn used() (activity_name in external_id(), entity_name in external_id(),namespace in namespace()) -> ActivityUses { - let activity = ActivityId::from_external_id(&activity_name); - let id = EntityId::from_external_id(&entity_name); - - ActivityUses { - namespace, - id, - activity - } - } + fn used() (activity_name in external_id(), entity_name in external_id(),namespace in namespace()) -> ActivityUses { + let activity = ActivityId::from_external_id(&activity_name); + let id = EntityId::from_external_id(&entity_name); + + ActivityUses { + namespace, + id, + activity + } + } } prop_compose! { - fn create_entity() (external_id in external_id(),namespace in namespace()) -> EntityExists { - EntityExists { - namespace, - external_id, - } - } + fn create_entity() (external_id in external_id(),namespace in namespace()) -> EntityExists { + EntityExists { + namespace, + external_id, + } + } } prop_compose! { - fn entity_derive() ( - external_id in external_id(), - used in external_id(), - namespace in namespace(), - ) -> EntityDerive { - let id = EntityId::from_external_id(&external_id); - let used_id = EntityId::from_external_id(&used); - - EntityDerive { - namespace, - id, - used_id, - activity_id: None, - typ: DerivationType::None - } - } + fn entity_derive() ( + external_id in external_id(), + used in external_id(), + namespace in namespace(), + ) -> EntityDerive { + let id = EntityId::from_external_id(&external_id); + let used_id = EntityId::from_external_id(&used); + + EntityDerive { + namespace, + id, + used_id, + activity_id: None, + typ: DerivationType::None + } + } } prop_compose! { - fn attribute() ( - typ in typ(), - ) -> Attribute{ - - Attribute { - typ, - value: serde_json::Value::String("data".to_owned()).into(), - } - } + fn attribute() ( + typ in typ(), + ) -> Attribute{ + + Attribute { + typ, + value: serde_json::Value::String("data".to_owned()).into(), + } + } } prop_compose! { - fn attributes() ( - attributes in prop::collection::vec(attribute(), 5), - typ in domain_type_id(), - ) -> Attributes { - - Attributes { - typ: Some(typ), - attributes: attributes.into_iter().map(|a| (a.typ.clone(), a)).collect(), - } - } + fn attributes() ( + attributes in prop::collection::vec(attribute(), 5), + typ in domain_type_id(), + ) -> Attributes { + + Attributes { + typ: Some(typ), + attributes: attributes.into_iter().map(|a| (a.typ.clone(), a)).collect(), + } + } } prop_compose! { - fn acted_on_behalf_of() ( - external_id in external_id(), - activity in option::of(external_id()), - role in option::of(external_id()), - delegate in external_id(), - namespace in namespace(), - ) -> ActsOnBehalfOf { - - let responsible_id = AgentId::from_external_id(&external_id); - let delegate_id = AgentId::from_external_id(&delegate); - let activity_id = activity.map(|a| ActivityId::from_external_id(&a)); - let id = DelegationId::from_component_ids(&delegate_id, &responsible_id, activity_id.as_ref(), role.as_ref().map(|x| x.as_str())); - - ActsOnBehalfOf { - id, - responsible_id, - delegate_id, - role: role.as_ref().map(|x| Role::from(x.as_str())), - activity_id, - namespace, - } - - } + fn acted_on_behalf_of() ( + external_id in external_id(), + activity in option::of(external_id()), + role in option::of(external_id()), + delegate in external_id(), + namespace in namespace(), + ) -> ActsOnBehalfOf { + + let responsible_id = AgentId::from_external_id(&external_id); + let delegate_id = AgentId::from_external_id(&delegate); + let activity_id = activity.map(|a| ActivityId::from_external_id(&a)); + let id = DelegationId::from_component_ids(&delegate_id, &responsible_id, activity_id.as_ref(), role.as_ref().map(|x| x.as_str())); + + ActsOnBehalfOf { + id, + responsible_id, + delegate_id, + role: role.as_ref().map(|x| Role::from(x.as_str())), + activity_id, + namespace, + } + + } } prop_compose! { - fn was_associated_with() ( - activity in external_id(), - role in option::of(external_id()), - agent in external_id(), - namespace in namespace(), - ) -> WasAssociatedWith { + fn was_associated_with() ( + activity in external_id(), + role in option::of(external_id()), + agent in external_id(), + namespace in namespace(), + ) -> WasAssociatedWith { - let agent_id = AgentId::from_external_id(&agent); - let activity_id = ActivityId::from_external_id(&activity); - let id = AssociationId::from_component_ids(&agent_id, &activity_id, role.as_ref().map(|x| x.as_str())); + let agent_id = AgentId::from_external_id(&agent); + let activity_id = ActivityId::from_external_id(&activity); + let id = AssociationId::from_component_ids(&agent_id, &activity_id, role.as_ref().map(|x| x.as_str())); - WasAssociatedWith{id,agent_id,activity_id,role:role.as_ref().map(Role::from), namespace } + WasAssociatedWith{id,agent_id,activity_id,role:role.as_ref().map(Role::from), namespace } - } + } } prop_compose! { - fn was_informed_by() ( - // we probably should disallow reflexivity for `wasInformedBy` - activity1 in external_id(), - activity2 in external_id(), - namespace in namespace(), - ) -> WasInformedBy { - - WasInformedBy{ - namespace, - activity: ActivityId::from_external_id(&activity1), - informing_activity: ActivityId::from_external_id(&activity2), - } - } + fn was_informed_by() ( + // we probably should disallow reflexivity for `wasInformedBy` + activity1 in external_id(), + activity2 in external_id(), + namespace in namespace(), + ) -> WasInformedBy { + + WasInformedBy{ + namespace, + activity: ActivityId::from_external_id(&activity1), + informing_activity: ActivityId::from_external_id(&activity2), + } + } } prop_compose! { - fn entity_attributes() ( - external_id in external_id(), - namespace in namespace(), - attributes in attributes(), - ) -> SetAttributes { - - SetAttributes::Entity{ - id: EntityId::from_external_id(&external_id), - namespace, - attributes, - } - } + fn entity_attributes() ( + external_id in external_id(), + namespace in namespace(), + attributes in attributes(), + ) -> SetAttributes { + + SetAttributes::Entity{ + id: EntityId::from_external_id(&external_id), + namespace, + attributes, + } + } } prop_compose! { - fn agent_attributes() ( - external_id in external_id(), - namespace in namespace(), - attributes in attributes(), - ) -> SetAttributes { - SetAttributes::Agent { - id: AgentId::from_external_id(&external_id), - namespace, - attributes, - } - } + fn agent_attributes() ( + external_id in external_id(), + namespace in namespace(), + attributes in attributes(), + ) -> SetAttributes { + SetAttributes::Agent { + id: AgentId::from_external_id(&external_id), + namespace, + attributes, + } + } } prop_compose! { - fn activity_attributes() ( - external_id in external_id(), - namespace in namespace(), - attributes in attributes(), - ) -> SetAttributes { - SetAttributes::Activity{ - id: ActivityId::from_external_id(&external_id), - namespace, - attributes, - } - } + fn activity_attributes() ( + external_id in external_id(), + namespace in namespace(), + attributes in attributes(), + ) -> SetAttributes { + SetAttributes::Activity{ + id: ActivityId::from_external_id(&external_id), + namespace, + attributes, + } + } } fn transaction() -> impl Strategy { - prop_oneof![ - 1 => create_agent().prop_map(ChronicleOperation::AgentExists), - 1 => create_activity().prop_map(ChronicleOperation::ActivityExists), - 1 => start_activity().prop_map(ChronicleOperation::StartActivity), - 1 => end_activity().prop_map(ChronicleOperation::EndActivity), - 1 => used().prop_map(ChronicleOperation::ActivityUses), - 1 => create_entity().prop_map(ChronicleOperation::EntityExists), - 1 => entity_derive().prop_map(ChronicleOperation::EntityDerive), - 1 => acted_on_behalf_of().prop_map(ChronicleOperation::AgentActsOnBehalfOf), - 1 => was_associated_with().prop_map(ChronicleOperation::WasAssociatedWith), - 1 => was_informed_by().prop_map(ChronicleOperation::WasInformedBy), - 1 => entity_attributes().prop_map(ChronicleOperation::SetAttributes), - 1 => activity_attributes().prop_map(ChronicleOperation::SetAttributes), - 1 => agent_attributes().prop_map(ChronicleOperation::SetAttributes), - ] + prop_oneof![ + 1 => create_agent().prop_map(ChronicleOperation::AgentExists), + 1 => create_activity().prop_map(ChronicleOperation::ActivityExists), + 1 => start_activity().prop_map(ChronicleOperation::StartActivity), + 1 => end_activity().prop_map(ChronicleOperation::EndActivity), + 1 => used().prop_map(ChronicleOperation::ActivityUses), + 1 => create_entity().prop_map(ChronicleOperation::EntityExists), + 1 => entity_derive().prop_map(ChronicleOperation::EntityDerive), + 1 => acted_on_behalf_of().prop_map(ChronicleOperation::AgentActsOnBehalfOf), + 1 => was_associated_with().prop_map(ChronicleOperation::WasAssociatedWith), + 1 => was_informed_by().prop_map(ChronicleOperation::WasInformedBy), + 1 => entity_attributes().prop_map(ChronicleOperation::SetAttributes), + 1 => activity_attributes().prop_map(ChronicleOperation::SetAttributes), + 1 => agent_attributes().prop_map(ChronicleOperation::SetAttributes), + ] } fn operation_seq() -> impl Strategy> { - proptest::collection::vec(transaction(), 1..50) -} - -fn compact_json(prov: &ProvModel) -> serde_json::Value { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - rt.block_on(async move { prov.to_json().compact().await }) - .unwrap() -} - -fn prov_from_json_ld(json: serde_json::Value) -> ProvModel { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - rt.block_on(async move { - let mut prov = ProvModel::default(); - prov.apply_json_ld(json).await.unwrap(); - prov - }) + proptest::collection::vec(transaction(), 1..50) } proptest! { + #![proptest_config(ProptestConfig { - max_shrink_iters: std::u32::MAX, verbose: 0, .. ProptestConfig::default() - })] - #[test] - fn operations(operations in operation_seq()) { - let mut prov = ProvModel::default(); - - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - - - // Keep track of the operations that were applied successfully - let mut applied_operations = vec![]; - // If we encounter a contradiction, store it along with the operation - // that caused it. prov will be in the final state before the contradiction - let mut contradiction: Option<(ChronicleOperation,Contradiction)> = None; - - // Apply each operation in order, stopping if any fail - for op in operations.iter() { - // Check that serialization of operation is symmetric - let op_json = op.to_json().0; - prop_assert_eq!(op, - &rt.block_on(ChronicleOperation::from_json(&op.to_json().0)).unwrap(), - "Serialized operation {}", serde_json::to_string_pretty(&op_json).unwrap()); - - let res = prov.apply(op); - if let Err(raised) = res { - contradiction = Some((op.clone(), raised)); - break; - } else { - applied_operations.push(op.clone()); - } - } - - // If we encountered a contradiction, check it is consistent with the - // operation - - if let Some((_op,Contradiction {id: _,namespace: _,contradiction})) = contradiction { - let _contradiction = contradiction.get(0).unwrap(); - } - - // Now assert that the final prov object matches what we would expect from the input operations - for op in applied_operations.iter() { - match op { - ChronicleOperation::CreateNamespace(CreateNamespace{id,external_id,uuid}) => { - prop_assert!(prov.namespaces.contains_key(id)); - let ns = prov.namespaces.get(id).unwrap(); - prop_assert_eq!(&ns.id, id); - prop_assert_eq!(&ns.external_id, external_id); - prop_assert_eq!(&ns.uuid, uuid); - }, - ChronicleOperation::AgentExists( - AgentExists { namespace, external_id}) => { - let agent = &prov.agents.get(&(namespace.to_owned(),AgentId::from_external_id(external_id))); - prop_assert!(agent.is_some()); - let agent = agent.unwrap(); - prop_assert_eq!(&agent.external_id, external_id); - prop_assert_eq!(&agent.namespaceid, namespace); - }, - ChronicleOperation::AgentActsOnBehalfOf( - ActsOnBehalfOf {namespace,id: _,delegate_id,activity_id, role, responsible_id } - ) => { - let agent = &prov.agents.get(&(namespace.to_owned(),responsible_id.to_owned())); - prop_assert!(agent.is_some()); - let agent = agent.unwrap(); - - let delegate = &prov.agents.get(&(namespace.to_owned(),delegate_id.to_owned())); - prop_assert!(delegate.is_some()); - let delegate = delegate.unwrap(); - - if let Some(activity_id) = activity_id { - let activity = &prov.activities.get(&(namespace.to_owned(),activity_id.to_owned())); - prop_assert!(activity.is_some()); - } - - let has_delegation = prov.delegation.get(&(namespace.to_owned(),responsible_id.to_owned())) - .unwrap() - .contains(&Delegation::new( - namespace, - &delegate.id, - &agent.id, - activity_id.as_ref(), - role.clone() - )); - - prop_assert!(has_delegation); - - } - ChronicleOperation::ActivityExists( - ActivityExists { namespace, external_id }) => { - let activity = &prov.activities.get(&(namespace.clone(),ActivityId::from_external_id(external_id))); - prop_assert!(activity.is_some()); - let activity = activity.unwrap(); - prop_assert_eq!(&activity.external_id, external_id); - prop_assert_eq!(&activity.namespaceid, namespace); - }, - ChronicleOperation::StartActivity( - StartActivity { namespace, id, time }) => { - let activity = &prov.activities.get(&(namespace.clone(),id.clone())); - prop_assert!(activity.is_some()); - let activity = activity.unwrap(); - prop_assert_eq!(&activity.external_id, id.external_id_part()); - prop_assert_eq!(&activity.namespaceid, namespace); - - prop_assert!(activity.started == Some(time.to_owned())); - }, - ChronicleOperation::EndActivity( - EndActivity { namespace, id, time }) => { - let activity = &prov.activities.get(&(namespace.to_owned(),id.to_owned())); - prop_assert!(activity.is_some()); - let activity = activity.unwrap(); - prop_assert_eq!(&activity.external_id, id.external_id_part()); - prop_assert_eq!(&activity.namespaceid, namespace); - - prop_assert!(activity.ended == Some(time.to_owned())); - } - ChronicleOperation::WasAssociatedWith(WasAssociatedWith { id : _, role, namespace, activity_id, agent_id }) => { - let has_asoc = prov.association.get(&(namespace.to_owned(), activity_id.to_owned())) - .unwrap() - .contains(&Association::new( - namespace, - agent_id, - activity_id, - role.clone()) - ); - - prop_assert!(has_asoc); - } - ChronicleOperation::WasAttributedTo(WasAttributedTo { id : _, role, namespace, entity_id, agent_id }) => { - let has_attribution = prov.attribution.get(&(namespace.to_owned(), entity_id.to_owned())) - .unwrap() - .contains(&Attribution::new( - namespace, - agent_id, - entity_id, - role.clone()) - ); - - prop_assert!(has_attribution); - } - ChronicleOperation::ActivityUses( - ActivityUses { namespace, id, activity }) => { - let activity_id = activity; - let entity = &prov.entities.get(&(namespace.to_owned(),id.to_owned())); - prop_assert!(entity.is_some()); - let entity = entity.unwrap(); - prop_assert_eq!(&entity.external_id, id.external_id_part()); - prop_assert_eq!(&entity.namespaceid, namespace); - - let activity = &prov.activities.get(&(namespace.to_owned(),activity_id.to_owned())); - prop_assert!(activity.is_some()); - let activity = activity.unwrap(); - prop_assert_eq!(&activity.external_id, activity_id.external_id_part()); - prop_assert_eq!(&activity.namespaceid, namespace); - - let has_usage = prov.usage.get(&(namespace.to_owned(), activity_id.to_owned())) - .unwrap() - .contains(&Usage { - activity_id: activity_id.clone(), - entity_id: id.clone(), - }); - - prop_assert!(has_usage); - }, - ChronicleOperation::EntityExists( - EntityExists { namespace, external_id}) => { - let entity = &prov.entities.get(&(namespace.to_owned(),EntityId::from_external_id(external_id))); - prop_assert!(entity.is_some()); - let entity = entity.unwrap(); - prop_assert_eq!(&entity.external_id, external_id); - prop_assert_eq!(&entity.namespaceid, namespace); - }, - ChronicleOperation::WasGeneratedBy(WasGeneratedBy{namespace, id, activity}) => { - let activity_id = activity; - let entity = &prov.entities.get(&(namespace.to_owned(),id.to_owned())); - prop_assert!(entity.is_some()); - let entity = entity.unwrap(); - prop_assert_eq!(&entity.external_id, id.external_id_part()); - prop_assert_eq!(&entity.namespaceid, namespace); - - let activity = &prov.activities.get(&(namespace.to_owned(),activity.to_owned())); - prop_assert!(activity.is_some()); - let activity = activity.unwrap(); - prop_assert_eq!(&activity.external_id, activity_id.external_id_part()); - prop_assert_eq!(&activity.namespaceid, namespace); - - let has_generation = prov.generation.get( - &(namespace.clone(),id.clone())) - .unwrap() - .contains(& Generation { - activity_id: activity_id.clone(), - generated_id: id.clone(), - }); - - prop_assert!(has_generation); - } - ChronicleOperation::WasInformedBy(WasInformedBy{namespace, activity, informing_activity}) => { - let informed_activity = &prov.activities.get(&(namespace.to_owned(), activity.to_owned())); - prop_assert!(informed_activity.is_some()); - let informed_activity = informed_activity.unwrap(); - prop_assert_eq!(&informed_activity.external_id, activity.external_id_part()); - prop_assert_eq!(&informed_activity.namespaceid, namespace); - - let was_informed_by = prov.was_informed_by.get( - &(namespace.clone(), activity.clone())) - .unwrap() - .contains(&(namespace.to_owned(), informing_activity.to_owned())); - - prop_assert!(was_informed_by); - }, - ChronicleOperation::EntityDerive(EntityDerive { - namespace, - id, - used_id, - activity_id, - typ, - }) => { - let generated_entity = &prov.entities.get(&(namespace.to_owned(),id.to_owned())); - prop_assert!(generated_entity.is_some()); - - let used_entity = &prov.entities.get(&(namespace.to_owned(),used_id.to_owned())); - prop_assert!(used_entity.is_some()); - - let has_derivation = prov.derivation.get( - &(namespace.clone(),id.clone())) - .unwrap() - .contains(& Derivation { - - used_id: used_id.clone(), - activity_id: activity_id.clone(), - generated_id: id.clone(), - typ: *typ - }); - - prop_assert!(has_derivation); - } - ChronicleOperation::SetAttributes( - SetAttributes::Entity { namespace, id, attributes}) => { - let entity = &prov.entities.get(&(namespace.to_owned(),id.to_owned())); - prop_assert!(entity.is_some()); - let entity = entity.unwrap(); - - prop_assert_eq!(&entity.domaintypeid, &attributes.typ); - }, - ChronicleOperation::SetAttributes(SetAttributes::Activity{ namespace, id, attributes}) => { - let activity = &prov.activities.get(&(namespace.to_owned(),id.to_owned())); - prop_assert!(activity.is_some()); - let activity = activity.unwrap(); - - prop_assert_eq!(&activity.domaintypeid, &attributes.typ); - }, - ChronicleOperation::SetAttributes(SetAttributes::Agent { namespace, id, attributes}) => { - let agent = &prov.agents.get(&(namespace.to_owned(),id.to_owned())); - prop_assert!(agent.is_some()); - let agent = agent.unwrap(); - - prop_assert_eq!(&agent.domaintypeid, &attributes.typ); - }, - } - } - - // Test that serialisation to and from JSON-LD is symmetric - let lhs_json_expanded = prov.to_json().0; - - let lhs_json = compact_json(&prov); - - let serialized_prov = prov_from_json_ld(lhs_json.clone()); - - - prop_assert_eq!(&prov, &serialized_prov, "Prov reserialisation compact: \n{} expanded \n {}", - serde_json::to_string_pretty(&lhs_json).unwrap(), serde_json::to_string_pretty(&lhs_json_expanded).unwrap()); - - // Test that serialisation to JSON-LD is deterministic - for _ in 0..10 { - let lhs_json_2 = compact_json(&prov).clone(); - prop_assert_eq!( lhs_json.clone().to_string(), lhs_json_2.to_string()); - } - } + max_shrink_iters: std::u32::MAX, verbose: 0, .. ProptestConfig::default() + })] + #[test] + fn operations(operations in operation_seq()) { + fn compact_json(prov: &ProvModel) -> serde_json::Value { + let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + + rt.block_on(async move { prov.to_json().compact().await }).unwrap() + } + + fn prov_from_json_ld(json: serde_json::Value) -> ProvModel { + let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + + rt.block_on(async move { + let mut prov = ProvModel::default(); + prov.apply_json_ld(json).await.unwrap(); + prov + }) + } + let mut prov = ProvModel::default(); + + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + + + // Keep track of the operations that were applied successfully + let mut applied_operations = vec![]; + // If we encounter a contradiction, store it along with the operation + // that caused it. prov will be in the final state before the contradiction + let mut contradiction: Option<(ChronicleOperation,Contradiction)> = None; + + // Apply each operation in order, stopping if any fail + for op in operations.iter() { + // Check that serialization of operation is symmetric + let op_json = op.to_json().0; + prop_assert_eq!(op, + &rt.block_on(ChronicleOperation::from_json(&op.to_json().0)).unwrap(), + "Serialized operation {}", serde_json::to_string_pretty(&op_json).unwrap()); + + let res = prov.apply(op); + if let Err(raised) = res { + contradiction = Some((op.clone(), raised)); + break; + } else { + applied_operations.push(op.clone()); + } + } + + // If we encountered a contradiction, check it is consistent with the + // operation + + if let Some((_op,Contradiction {id: _,namespace: _,contradiction})) = contradiction { + let _contradiction = contradiction.get(0).unwrap(); + } + + // Now assert that the final prov object matches what we would expect from the input operations + for op in applied_operations.iter() { + match op { + ChronicleOperation::CreateNamespace(CreateNamespace{id,external_id,uuid}) => { + prop_assert!(prov.namespaces.contains_key(id)); + let ns = prov.namespaces.get(id).unwrap(); + prop_assert_eq!(&ns.id, id); + prop_assert_eq!(&ns.external_id, external_id); + prop_assert_eq!(&ns.uuid, uuid); + }, + ChronicleOperation::AgentExists( + AgentExists { namespace, external_id}) => { + let agent = &prov.agents.get(&(namespace.to_owned(),AgentId::from_external_id(external_id))); + prop_assert!(agent.is_some()); + let agent = agent.unwrap(); + prop_assert_eq!(&agent.external_id, external_id); + prop_assert_eq!(&agent.namespaceid, namespace); + }, + ChronicleOperation::AgentActsOnBehalfOf( + ActsOnBehalfOf {namespace,id: _,delegate_id,activity_id, role, responsible_id } + ) => { + let agent = &prov.agents.get(&(namespace.to_owned(),responsible_id.to_owned())); + prop_assert!(agent.is_some()); + let agent = agent.unwrap(); + + let delegate = &prov.agents.get(&(namespace.to_owned(),delegate_id.to_owned())); + prop_assert!(delegate.is_some()); + let delegate = delegate.unwrap(); + + if let Some(activity_id) = activity_id { + let activity = &prov.activities.get(&(namespace.to_owned(),activity_id.to_owned())); + prop_assert!(activity.is_some()); + } + + let has_delegation = prov.delegation.get(&(namespace.to_owned(),responsible_id.to_owned())) + .unwrap() + .contains(&Delegation::new( + namespace, + &delegate.id, + &agent.id, + activity_id.as_ref(), + role.clone() + )); + + prop_assert!(has_delegation); + + } + ChronicleOperation::ActivityExists( + ActivityExists { namespace, external_id }) => { + let activity = &prov.activities.get(&(namespace.clone(),ActivityId::from_external_id(external_id))); + prop_assert!(activity.is_some()); + let activity = activity.unwrap(); + prop_assert_eq!(&activity.external_id, external_id); + prop_assert_eq!(&activity.namespaceid, namespace); + }, + ChronicleOperation::StartActivity( + StartActivity { namespace, id, time }) => { + let activity = &prov.activities.get(&(namespace.clone(),id.clone())); + prop_assert!(activity.is_some()); + let activity = activity.unwrap(); + prop_assert_eq!(&activity.external_id, id.external_id_part()); + prop_assert_eq!(&activity.namespaceid, namespace); + + prop_assert!(activity.started == Some(time.to_owned())); + }, + ChronicleOperation::EndActivity( + EndActivity { namespace, id, time }) => { + let activity = &prov.activities.get(&(namespace.to_owned(),id.to_owned())); + prop_assert!(activity.is_some()); + let activity = activity.unwrap(); + prop_assert_eq!(&activity.external_id, id.external_id_part()); + prop_assert_eq!(&activity.namespaceid, namespace); + + prop_assert!(activity.ended == Some(time.to_owned())); + } + ChronicleOperation::WasAssociatedWith(WasAssociatedWith { id : _, role, namespace, activity_id, agent_id }) => { + let has_asoc = prov.association.get(&(namespace.to_owned(), activity_id.to_owned())) + .unwrap() + .contains(&Association::new( + namespace, + agent_id, + activity_id, + role.clone()) + ); + + prop_assert!(has_asoc); + } + ChronicleOperation::WasAttributedTo(WasAttributedTo { id : _, role, namespace, entity_id, agent_id }) => { + let has_attribution = prov.attribution.get(&(namespace.to_owned(), entity_id.to_owned())) + .unwrap() + .contains(&Attribution::new( + namespace, + agent_id, + entity_id, + role.clone()) + ); + + prop_assert!(has_attribution); + } + ChronicleOperation::ActivityUses( + ActivityUses { namespace, id, activity }) => { + let activity_id = activity; + let entity = &prov.entities.get(&(namespace.to_owned(),id.to_owned())); + prop_assert!(entity.is_some()); + let entity = entity.unwrap(); + prop_assert_eq!(&entity.external_id, id.external_id_part()); + prop_assert_eq!(&entity.namespaceid, namespace); + + let activity = &prov.activities.get(&(namespace.to_owned(),activity_id.to_owned())); + prop_assert!(activity.is_some()); + let activity = activity.unwrap(); + prop_assert_eq!(&activity.external_id, activity_id.external_id_part()); + prop_assert_eq!(&activity.namespaceid, namespace); + + let has_usage = prov.usage.get(&(namespace.to_owned(), activity_id.to_owned())) + .unwrap() + .contains(&Usage { + activity_id: activity_id.clone(), + entity_id: id.clone(), + }); + + prop_assert!(has_usage); + }, + ChronicleOperation::EntityExists( + EntityExists { namespace, external_id}) => { + let entity = &prov.entities.get(&(namespace.to_owned(),EntityId::from_external_id(external_id))); + prop_assert!(entity.is_some()); + let entity = entity.unwrap(); + prop_assert_eq!(&entity.external_id, external_id); + prop_assert_eq!(&entity.namespaceid, namespace); + }, + ChronicleOperation::WasGeneratedBy(WasGeneratedBy{namespace, id, activity}) => { + let activity_id = activity; + let entity = &prov.entities.get(&(namespace.to_owned(),id.to_owned())); + prop_assert!(entity.is_some()); + let entity = entity.unwrap(); + prop_assert_eq!(&entity.external_id, id.external_id_part()); + prop_assert_eq!(&entity.namespaceid, namespace); + + let activity = &prov.activities.get(&(namespace.to_owned(),activity.to_owned())); + prop_assert!(activity.is_some()); + let activity = activity.unwrap(); + prop_assert_eq!(&activity.external_id, activity_id.external_id_part()); + prop_assert_eq!(&activity.namespaceid, namespace); + + let has_generation = prov.generation.get( + &(namespace.clone(),id.clone())) + .unwrap() + .contains(& Generation { + activity_id: activity_id.clone(), + generated_id: id.clone(), + }); + + prop_assert!(has_generation); + } + ChronicleOperation::WasInformedBy(WasInformedBy{namespace, activity, informing_activity}) => { + let informed_activity = &prov.activities.get(&(namespace.to_owned(), activity.to_owned())); + prop_assert!(informed_activity.is_some()); + let informed_activity = informed_activity.unwrap(); + prop_assert_eq!(&informed_activity.external_id, activity.external_id_part()); + prop_assert_eq!(&informed_activity.namespaceid, namespace); + + let was_informed_by = prov.was_informed_by.get( + &(namespace.clone(), activity.clone())) + .unwrap() + .contains(&(namespace.to_owned(), informing_activity.to_owned())); + + prop_assert!(was_informed_by); + }, + ChronicleOperation::EntityDerive(EntityDerive { + namespace, + id, + used_id, + activity_id, + typ, + }) => { + let generated_entity = &prov.entities.get(&(namespace.to_owned(),id.to_owned())); + prop_assert!(generated_entity.is_some()); + + let used_entity = &prov.entities.get(&(namespace.to_owned(),used_id.to_owned())); + prop_assert!(used_entity.is_some()); + + let has_derivation = prov.derivation.get( + &(namespace.clone(),id.clone())) + .unwrap() + .contains(& Derivation { + + used_id: used_id.clone(), + activity_id: activity_id.clone(), + generated_id: id.clone(), + typ: *typ + }); + + prop_assert!(has_derivation); + } + ChronicleOperation::SetAttributes( + SetAttributes::Entity { namespace, id, attributes}) => { + let entity = &prov.entities.get(&(namespace.to_owned(),id.to_owned())); + prop_assert!(entity.is_some()); + let entity = entity.unwrap(); + + prop_assert_eq!(&entity.domaintypeid, &attributes.typ); + }, + ChronicleOperation::SetAttributes(SetAttributes::Activity{ namespace, id, attributes}) => { + let activity = &prov.activities.get(&(namespace.to_owned(),id.to_owned())); + prop_assert!(activity.is_some()); + let activity = activity.unwrap(); + + prop_assert_eq!(&activity.domaintypeid, &attributes.typ); + }, + ChronicleOperation::SetAttributes(SetAttributes::Agent { namespace, id, attributes}) => { + let agent = &prov.agents.get(&(namespace.to_owned(),id.to_owned())); + prop_assert!(agent.is_some()); + let agent = agent.unwrap(); + + prop_assert_eq!(&agent.domaintypeid, &attributes.typ); + }, + } + } + + // Test that serialisation to and from JSON-LD is symmetric + let lhs_json_expanded = prov.to_json().0; + + let lhs_json = compact_json(&prov); + + let serialized_prov = prov_from_json_ld(lhs_json.clone()); + + + prop_assert_eq!(&prov, &serialized_prov, "Prov reserialisation compact: \n{} expanded \n {}", + serde_json::to_string_pretty(&lhs_json).unwrap(), serde_json::to_string_pretty(&lhs_json_expanded).unwrap()); + + // Test that serialisation to JSON-LD is deterministic + for _ in 0..10 { + let lhs_json_2 = compact_json(&prov).clone(); + prop_assert_eq!( lhs_json.clone().to_string(), lhs_json_2.to_string()); + } + } } diff --git a/crates/common/src/prov/model/to_json_ld.rs b/crates/common/src/prov/model/to_json_ld.rs deleted file mode 100644 index fb641d90a..000000000 --- a/crates/common/src/prov/model/to_json_ld.rs +++ /dev/null @@ -1,1056 +0,0 @@ -use iref::{AsIri, Iri}; -use serde_json::{json, Value}; - -use crate::{ - attributes::{Attribute, Attributes}, - prov::{ - operations::{ChronicleOperation, CreateNamespace, DerivationType}, - vocab::{Chronicle, ChronicleOperations, Prov}, - ChronicleIri, ExternalIdPart, FromCompact, UuidPart, - }, -}; - -use super::{ExpandedJson, ProvModel}; -use crate::prov::operations::*; -pub trait ToJson { - fn to_json(&self) -> ExpandedJson; -} - -impl ToJson for ProvModel { - /// Write the model out as a JSON-LD document in expanded form - fn to_json(&self) -> ExpandedJson { - let mut doc = Vec::new(); - - for (id, ns) in self.namespaces.iter() { - doc.push(json!({ - "@id": (*id.de_compact()), - "@type": [Iri::from(Chronicle::Namespace).as_str()], - "http://btp.works/chronicle/ns#externalId": [{ - "@value": ns.external_id.as_str(), - }] - })) - } - - for ((_, id), agent) in self.agents.iter() { - let mut typ = vec![Iri::from(Prov::Agent).to_string()]; - if let Some(x) = agent.domaintypeid.as_ref() { - typ.push(x.de_compact()) - } - - if let Value::Object(mut agentdoc) = json!({ - "@id": (*id.de_compact()), - "@type": typ, - "http://btp.works/chronicle/ns#externalId": [{ - "@value": agent.external_id.as_str(), - }] - }) { - let agent_key = (agent.namespaceid.clone(), agent.id.clone()); - - if let Some(delegation) = self - .acted_on_behalf_of - .get(&(agent.namespaceid.to_owned(), id.to_owned())) - { - let mut ids = Vec::new(); - let mut qualified_ids = Vec::new(); - - for delegation in delegation.iter() { - ids.push(json!({"@id": delegation.responsible_id.de_compact()})); - qualified_ids.push(json!({"@id": delegation.id.de_compact()})); - } - - agentdoc.insert( - Iri::from(Prov::ActedOnBehalfOf).to_string(), - Value::Array(ids), - ); - - agentdoc.insert( - Iri::from(Prov::QualifiedDelegation).to_string(), - Value::Array(qualified_ids), - ); - } - - let mut values = Vec::new(); - - values.push(json!({ - "@id": Value::String(agent.namespaceid.de_compact()), - })); - - agentdoc.insert( - Iri::from(Chronicle::HasNamespace).to_string(), - Value::Array(values), - ); - - Self::write_attributes(&mut agentdoc, agent.attributes.values()); - - doc.push(Value::Object(agentdoc)); - } - } - - for (_, associations) in self.association.iter() { - for association in (*associations).iter() { - if let Value::Object(mut associationdoc) = json!({ - "@id": association.id.de_compact(), - "@type": [Iri::from(Prov::Association).as_str()], - }) { - let mut values = Vec::new(); - - values.push(json!({ - "@id": Value::String(association.agent_id.de_compact()), - })); - - associationdoc.insert( - Iri::from(Prov::Responsible).to_string(), - Value::Array(values), - ); - - associationdoc.insert( - Iri::from(Prov::HadActivity).to_string(), - Value::Array(vec![json!({ - "@id": Value::String(association.activity_id.de_compact()), - })]), - ); - - if let Some(role) = &association.role { - associationdoc.insert( - Iri::from(Prov::HadRole).to_string(), - json!([{ "@value": role.to_string()}]), - ); - } - - let mut values = Vec::new(); - - values.push(json!({ - "@id": Value::String(association.namespace_id.de_compact()), - })); - - associationdoc.insert( - Iri::from(Chronicle::HasNamespace).to_string(), - Value::Array(values), - ); - - doc.push(Value::Object(associationdoc)); - } - } - } - - for (_, attributions) in self.attribution.iter() { - for attribution in (*attributions).iter() { - if let Value::Object(mut attribution_doc) = json!({ - "@id": attribution.id.de_compact(), - "@type": [Iri::from(Prov::Attribution).as_str()], - }) { - let mut values = Vec::new(); - - values.push(json!({ - "@id": Value::String(attribution.agent_id.de_compact()), - })); - - attribution_doc.insert( - Iri::from(Prov::Responsible).to_string(), - Value::Array(values), - ); - - attribution_doc.insert( - Iri::from(Prov::HadEntity).to_string(), - Value::Array(vec![json!({ - "@id": Value::String(attribution.entity_id.de_compact()), - })]), - ); - - if let Some(role) = &attribution.role { - attribution_doc.insert( - Iri::from(Prov::HadRole).to_string(), - json!([{ "@value": role.to_string()}]), - ); - } - - let mut values = Vec::new(); - - values.push(json!({ - "@id": Value::String(attribution.namespace_id.de_compact()), - })); - - attribution_doc.insert( - Iri::from(Chronicle::HasNamespace).to_string(), - Value::Array(values), - ); - - doc.push(Value::Object(attribution_doc)); - } - } - } - - for (_, delegations) in self.delegation.iter() { - for delegation in (*delegations).iter() { - if let Value::Object(mut delegationdoc) = json!({ - "@id": delegation.id.de_compact(), - "@type": [Iri::from(Prov::Delegation).as_str()], - }) { - if let Some(activity_id) = &delegation.activity_id { - delegationdoc.insert( - Iri::from(Prov::HadActivity).to_string(), - Value::Array(vec![json!({ - "@id": Value::String(activity_id.de_compact()), - })]), - ); - } - - if let Some(role) = &delegation.role { - delegationdoc.insert( - Iri::from(Prov::HadRole).to_string(), - json!([{ "@value": role.to_string()}]), - ); - } - - let mut responsible_ids = Vec::new(); - responsible_ids.push( - json!({ "@id": Value::String(delegation.responsible_id.de_compact())}), - ); - - delegationdoc.insert( - Iri::from(Prov::ActedOnBehalfOf).to_string(), - Value::Array(responsible_ids), - ); - - let mut delegate_ids = Vec::new(); - delegate_ids - .push(json!({ "@id": Value::String(delegation.delegate_id.de_compact())})); - - delegationdoc.insert( - Iri::from(Prov::Delegate).to_string(), - Value::Array(delegate_ids), - ); - - let mut values = Vec::new(); - - values.push(json!({ - "@id": Value::String(delegation.namespace_id.de_compact()), - })); - - delegationdoc.insert( - Iri::from(Chronicle::HasNamespace).to_string(), - Value::Array(values), - ); - - doc.push(Value::Object(delegationdoc)); - } - } - } - - for ((namespace, id), activity) in self.activities.iter() { - let mut typ = vec![Iri::from(Prov::Activity).de_compact()]; - if let Some(x) = activity.domaintypeid.as_ref() { - typ.push(x.de_compact()) - } - - if let Value::Object(mut activitydoc) = json!({ - "@id": (*id.de_compact()), - "@type": typ, - "http://btp.works/chronicle/ns#externalId": [{ - "@value": activity.external_id.as_str(), - }] - }) { - if let Some(time) = activity.started { - let mut values = Vec::new(); - values.push(json!({"@value": time.to_rfc3339()})); - - activitydoc.insert( - "http://www.w3.org/ns/prov#startedAtTime".to_string(), - Value::Array(values), - ); - } - - if let Some(time) = activity.ended { - let mut values = Vec::new(); - values.push(json!({"@value": time.to_rfc3339()})); - - activitydoc.insert( - "http://www.w3.org/ns/prov#endedAtTime".to_string(), - Value::Array(values), - ); - } - - if let Some(asoc) = self.association.get(&(namespace.to_owned(), id.to_owned())) { - let mut ids = Vec::new(); - - let mut qualified_ids = Vec::new(); - for asoc in asoc.iter() { - ids.push(json!({"@id": asoc.agent_id.de_compact()})); - qualified_ids.push(json!({"@id": asoc.id.de_compact()})); - } - - activitydoc.insert( - Iri::from(Prov::WasAssociatedWith).de_compact(), - Value::Array(ids), - ); - - activitydoc.insert( - Iri::from(Prov::QualifiedAssociation).de_compact(), - Value::Array(qualified_ids), - ); - } - - if let Some(usage) = self.usage.get(&(namespace.to_owned(), id.to_owned())) { - let mut ids = Vec::new(); - - for usage in usage.iter() { - ids.push(json!({"@id": usage.entity_id.de_compact()})); - } - - activitydoc.insert(Iri::from(Prov::Used).de_compact(), Value::Array(ids)); - } - - let mut values = Vec::new(); - - values.push(json!({ - "@id": Value::String(activity.namespaceid.de_compact()), - })); - - activitydoc.insert( - Iri::from(Chronicle::HasNamespace).to_string(), - Value::Array(values), - ); - - if let Some(activities) = self - .was_informed_by - .get(&(namespace.to_owned(), id.to_owned())) - { - let mut values = Vec::new(); - - for (_, activity) in (*activities).iter() { - values.push(json!({ - "@id": Value::String(activity.de_compact()), - })); - } - activitydoc.insert( - Iri::from(Prov::WasInformedBy).to_string(), - Value::Array(values), - ); - } - - Self::write_attributes(&mut activitydoc, activity.attributes.values()); - - doc.push(Value::Object(activitydoc)); - } - } - - for ((namespace, id), entity) in self.entities.iter() { - let mut typ = vec![Iri::from(Prov::Entity).de_compact()]; - if let Some(x) = entity.domaintypeid.as_ref() { - typ.push(x.de_compact()) - } - - if let Value::Object(mut entitydoc) = json!({ - "@id": (*id.de_compact()), - "@type": typ, - "http://btp.works/chronicle/ns#externalId": [{ - "@value": entity.external_id.as_str() - }] - }) { - if let Some(derivation) = - self.derivation.get(&(namespace.to_owned(), id.to_owned())) - { - let mut derived_ids = Vec::new(); - let mut primary_ids = Vec::new(); - let mut quotation_ids = Vec::new(); - let mut revision_ids = Vec::new(); - - for derivation in derivation.iter() { - let id = json!({"@id": derivation.used_id.de_compact()}); - match derivation.typ { - DerivationType::PrimarySource => primary_ids.push(id), - DerivationType::Quotation => quotation_ids.push(id), - DerivationType::Revision => revision_ids.push(id), - DerivationType::None => derived_ids.push(id), - } - } - if !derived_ids.is_empty() { - entitydoc.insert( - Iri::from(Prov::WasDerivedFrom).to_string(), - Value::Array(derived_ids), - ); - } - if !primary_ids.is_empty() { - entitydoc.insert( - Iri::from(Prov::HadPrimarySource).to_string(), - Value::Array(primary_ids), - ); - } - if !quotation_ids.is_empty() { - entitydoc.insert( - Iri::from(Prov::WasQuotedFrom).to_string(), - Value::Array(quotation_ids), - ); - } - if !revision_ids.is_empty() { - entitydoc.insert( - Iri::from(Prov::WasRevisionOf).to_string(), - Value::Array(revision_ids), - ); - } - } - - if let Some(generation) = - self.generation.get(&(namespace.to_owned(), id.to_owned())) - { - let mut ids = Vec::new(); - - for generation in generation.iter() { - ids.push(json!({"@id": generation.activity_id.de_compact()})); - } - - entitydoc.insert( - Iri::from(Prov::WasGeneratedBy).to_string(), - Value::Array(ids), - ); - } - - let entity_key = (entity.namespaceid.clone(), entity.id.clone()); - - if let Some(attributions) = self.attribution.get(&entity_key) { - let mut ids = Vec::new(); - - let mut qualified_ids = Vec::new(); - for attribution in attributions.iter() { - ids.push(json!({"@id": attribution.agent_id.de_compact()})); - qualified_ids.push(json!({"@id": attribution.id.de_compact()})); - } - - entitydoc.insert( - Iri::from(Prov::WasAttributedTo).de_compact(), - Value::Array(ids), - ); - - entitydoc.insert( - Iri::from(Prov::QualifiedAttribution).de_compact(), - Value::Array(qualified_ids), - ); - } - - let mut values = Vec::new(); - - values.push(json!({ - "@id": Value::String(entity.namespaceid.de_compact()), - })); - - entitydoc.insert( - Iri::from(Chronicle::HasNamespace).to_string(), - Value::Array(values), - ); - - Self::write_attributes(&mut entitydoc, entity.attributes.values()); - - doc.push(Value::Object(entitydoc)); - } - } - - ExpandedJson(Value::Array(doc)) - } -} - -impl ProvModel { - fn write_attributes<'a, I: Iterator>( - doc: &mut serde_json::Map, - attributes: I, - ) { - let mut attribute_node = serde_json::Map::new(); - - for attribute in attributes { - attribute_node.insert(attribute.typ.clone(), attribute.value.0.clone()); - } - - doc.insert( - Chronicle::Value.as_iri().to_string(), - json!([{"@value" : Value::Object(attribute_node), "@type": "@json"}]), - ); - } -} - -impl ToJson for ChronicleOperation { - fn to_json(&self) -> ExpandedJson { - let mut operation: Vec = Vec::new(); - - let o = match self { - ChronicleOperation::CreateNamespace(CreateNamespace { id, .. }) => { - let mut o = Value::new_operation(ChronicleOperations::CreateNamespace); - - o.has_value( - OperationValue::string(id.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(id.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o - } - ChronicleOperation::AgentExists(AgentExists { - namespace, - external_id, - }) => { - let mut o = Value::new_operation(ChronicleOperations::AgentExists); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(external_id), - ChronicleOperations::AgentName, - ); - - o - } - ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf { - namespace, - id: _, // This is derivable from components - delegate_id, - activity_id, - role, - responsible_id, - }) => { - let mut o = Value::new_operation(ChronicleOperations::AgentActsOnBehalfOf); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(delegate_id.external_id_part()), - ChronicleOperations::DelegateId, - ); - - o.has_value( - OperationValue::string(responsible_id.external_id_part()), - ChronicleOperations::ResponsibleId, - ); - - if let Some(role) = role { - o.has_value(OperationValue::string(role), ChronicleOperations::Role); - } - - if let Some(activity_id) = activity_id { - o.has_value( - OperationValue::string(activity_id.external_id_part()), - ChronicleOperations::ActivityName, - ); - } - - o - } - ChronicleOperation::ActivityExists(ActivityExists { - namespace, - external_id, - }) => { - let mut o = Value::new_operation(ChronicleOperations::ActivityExists); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(external_id), - ChronicleOperations::ActivityName, - ); - - o - } - ChronicleOperation::StartActivity(StartActivity { - namespace, - id, - time, - }) => { - let mut o = Value::new_operation(ChronicleOperations::StartActivity); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(id.external_id_part()), - ChronicleOperations::ActivityName, - ); - - o.has_value( - OperationValue::string(time.to_rfc3339()), - ChronicleOperations::StartActivityTime, - ); - - o - } - ChronicleOperation::EndActivity(EndActivity { - namespace, - id, - time, - }) => { - let mut o = Value::new_operation(ChronicleOperations::EndActivity); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(id.external_id_part()), - ChronicleOperations::ActivityName, - ); - - o.has_value( - OperationValue::string(time.to_rfc3339()), - ChronicleOperations::EndActivityTime, - ); - - o - } - ChronicleOperation::ActivityUses(ActivityUses { - namespace, - id, - activity, - }) => { - let mut o = Value::new_operation(ChronicleOperations::ActivityUses); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(id.external_id_part()), - ChronicleOperations::EntityName, - ); - - o.has_value( - OperationValue::string(activity.external_id_part()), - ChronicleOperations::ActivityName, - ); - - o - } - ChronicleOperation::EntityExists(EntityExists { - namespace, - external_id, - }) => { - let mut o = Value::new_operation(ChronicleOperations::EntityExists); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(external_id), - ChronicleOperations::EntityName, - ); - - o - } - ChronicleOperation::WasGeneratedBy(WasGeneratedBy { - namespace, - id, - activity, - }) => { - let mut o = Value::new_operation(ChronicleOperations::WasGeneratedBy); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(id.external_id_part()), - ChronicleOperations::EntityName, - ); - - o.has_value( - OperationValue::string(activity.external_id_part()), - ChronicleOperations::ActivityName, - ); - - o - } - ChronicleOperation::WasInformedBy(WasInformedBy { - namespace, - activity, - informing_activity, - }) => { - let mut o = Value::new_operation(ChronicleOperations::WasInformedBy); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(activity.external_id_part()), - ChronicleOperations::ActivityName, - ); - - o.has_value( - OperationValue::string(informing_activity.external_id_part()), - ChronicleOperations::InformingActivityName, - ); - - o - } - ChronicleOperation::EntityDerive(EntityDerive { - namespace, - id, - used_id, - activity_id, - typ, - }) => { - let mut o = Value::new_operation(ChronicleOperations::EntityDerive); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(id.external_id_part()), - ChronicleOperations::EntityName, - ); - - o.has_value( - OperationValue::string(used_id.external_id_part()), - ChronicleOperations::UsedEntityName, - ); - - if let Some(activity) = activity_id { - o.has_value( - OperationValue::string(activity.external_id_part()), - ChronicleOperations::ActivityName, - ); - } - - if *typ != DerivationType::None { - o.derivation(typ); - } - - o - } - ChronicleOperation::SetAttributes(SetAttributes::Entity { - namespace, - id, - attributes, - }) => { - let mut o = Value::new_operation(ChronicleOperations::SetAttributes); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(id.external_id_part()), - ChronicleOperations::EntityName, - ); - - if let Some(domaintypeid) = &attributes.typ { - let id = OperationValue::string(domaintypeid.external_id_part()); - o.has_value(id, ChronicleOperations::DomaintypeId); - } - - o.attributes_object(attributes); - - o - } - ChronicleOperation::SetAttributes(SetAttributes::Activity { - namespace, - id, - attributes, - }) => { - let mut o = Value::new_operation(ChronicleOperations::SetAttributes); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(id.external_id_part()), - ChronicleOperations::ActivityName, - ); - - if let Some(domaintypeid) = &attributes.typ { - let id = OperationValue::string(domaintypeid.external_id_part()); - o.has_value(id, ChronicleOperations::DomaintypeId); - } - - o.attributes_object(attributes); - - o - } - ChronicleOperation::SetAttributes(SetAttributes::Agent { - namespace, - id, - attributes, - }) => { - let mut o = Value::new_operation(ChronicleOperations::SetAttributes); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(id.external_id_part()), - ChronicleOperations::AgentName, - ); - - if let Some(domaintypeid) = &attributes.typ { - let id = OperationValue::string(domaintypeid.external_id_part()); - o.has_value(id, ChronicleOperations::DomaintypeId); - } - - o.attributes_object(attributes); - - o - } - ChronicleOperation::WasAssociatedWith(WasAssociatedWith { - id: _, - role, - namespace, - activity_id, - agent_id, - }) => { - let mut o = Value::new_operation(ChronicleOperations::WasAssociatedWith); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(activity_id.external_id_part()), - ChronicleOperations::ActivityName, - ); - - o.has_value( - OperationValue::string(agent_id.external_id_part()), - ChronicleOperations::AgentName, - ); - - if let Some(role) = role { - o.has_value(OperationValue::string(role), ChronicleOperations::Role); - } - - o - } - ChronicleOperation::WasAttributedTo(WasAttributedTo { - id: _, - role, - namespace, - entity_id, - agent_id, - }) => { - let mut o = Value::new_operation(ChronicleOperations::WasAttributedTo); - - o.has_value( - OperationValue::string(namespace.external_id_part()), - ChronicleOperations::NamespaceName, - ); - - o.has_value( - OperationValue::string(namespace.uuid_part()), - ChronicleOperations::NamespaceUuid, - ); - - o.has_value( - OperationValue::string(entity_id.external_id_part()), - ChronicleOperations::EntityName, - ); - - o.has_value( - OperationValue::string(agent_id.external_id_part()), - ChronicleOperations::AgentName, - ); - - if let Some(role) = role { - o.has_value(OperationValue::string(role), ChronicleOperations::Role); - } - - o - } - }; - operation.push(o); - super::ExpandedJson(operation.into()) - } -} - -struct OperationValue(String); - -impl OperationValue { - fn string(value: impl ToString) -> Self { - OperationValue(value.to_string()) - } - - #[allow(dead_code)] - fn identity(id: ChronicleIri) -> Self { - OperationValue(id.to_string()) - } -} - -trait Operate { - fn new_operation(op: ChronicleOperations) -> Self; - fn new_type(id: OperationValue, op: ChronicleOperations) -> Self; - fn new_value(id: OperationValue) -> Self; - fn new_id(id: OperationValue) -> Self; - fn has_value(&mut self, value: OperationValue, op: ChronicleOperations); - fn has_id(&mut self, id: OperationValue, op: ChronicleOperations); - fn attributes_object(&mut self, attributes: &Attributes); - fn derivation(&mut self, typ: &DerivationType); -} - -impl Operate for Value { - fn new_type(id: OperationValue, op: ChronicleOperations) -> Self { - json!({ - "@id": id.0, - "@type": [iref::Iri::from(op).as_str()], - }) - } - - fn new_value(id: OperationValue) -> Self { - json!({ - "@value": id.0 - }) - } - - fn new_id(id: OperationValue) -> Self { - json!({ - "@id": id.0 - }) - } - - fn has_value(&mut self, value: OperationValue, op: ChronicleOperations) { - if let Value::Object(map) = self { - let key = iref::Iri::from(op).to_string(); - let mut values: Vec = Vec::new(); - let object = Self::new_value(value); - values.push(object); - map.insert(key, Value::Array(values)); - } else { - panic!("use on JSON objects only"); - } - } - - fn has_id(&mut self, id: OperationValue, op: ChronicleOperations) { - if let Value::Object(map) = self { - let key = iref::Iri::from(op).to_string(); - let mut value: Vec = Vec::new(); - let object = Self::new_id(id); - value.push(object); - map.insert(key, Value::Array(value)); - } else { - panic!("use on JSON objects only"); - } - } - - fn new_operation(op: ChronicleOperations) -> Self { - let id = OperationValue::string("_:n1"); - Self::new_type(id, op) - } - - fn attributes_object(&mut self, attributes: &Attributes) { - if let Value::Object(map) = self { - let mut attribute_node = serde_json::Map::new(); - for attribute in attributes.attributes.values() { - attribute_node.insert(attribute.typ.clone(), attribute.value.0.clone()); - } - map.insert( - iref::Iri::from(ChronicleOperations::Attributes).to_string(), - json!([{"@value" : attribute_node, "@type": "@json"}]), - ); - } else { - panic!("use on JSON objects only"); - } - } - - fn derivation(&mut self, typ: &DerivationType) { - let typ = match typ { - DerivationType::None => panic!("should never handle a None derivation type"), - DerivationType::Revision => "Revision", - DerivationType::Quotation => "Quotation", - DerivationType::PrimarySource => "PrimarySource", - }; - let id = OperationValue::string(typ); - - self.has_value(id, ChronicleOperations::DerivationType); - } -} diff --git a/crates/common/src/prov/model/transaction/mod.rs b/crates/common/src/prov/model/transaction/mod.rs index e13d39627..ffb3f159f 100644 --- a/crates/common/src/prov/model/transaction/mod.rs +++ b/crates/common/src/prov/model/transaction/mod.rs @@ -5,66 +5,66 @@ pub mod v2; pub use v2::ChronicleTransaction; impl From for v2::ChronicleTransaction { - fn from(transaction: v1::ChronicleTransaction) -> Self { - v2::ChronicleTransaction::new(transaction.tx, transaction.identity) - } + fn from(transaction: v1::ChronicleTransaction) -> Self { + v2::ChronicleTransaction::new(transaction.tx, transaction.identity) + } } pub trait HasVersion { - fn get_version(&self) -> u16; + fn get_version(&self) -> u16; } impl HasVersion for v1::ChronicleTransaction { - fn get_version(&self) -> u16 { - 1 - } + fn get_version(&self) -> u16 { + 1 + } } impl HasVersion for v2::ChronicleTransaction { - fn get_version(&self) -> u16 { - 2 - } + fn get_version(&self) -> u16 { + 2 + } } pub const CURRENT_VERSION: u16 = 2; pub trait ToChronicleTransaction { - fn to_current(self) -> ChronicleTransaction; + fn to_current(self) -> ChronicleTransaction; } impl ToChronicleTransaction for v1::ChronicleTransaction { - fn to_current(self) -> ChronicleTransaction { - self.into() - } + fn to_current(self) -> ChronicleTransaction { + self.into() + } } impl ToChronicleTransaction for v2::ChronicleTransaction { - fn to_current(self) -> ChronicleTransaction { - self - } + fn to_current(self) -> ChronicleTransaction { + self + } } #[cfg(test)] mod test { - use super::{HasVersion, ToChronicleTransaction}; - use crate::identity::SignedIdentity; + use super::{HasVersion, ToChronicleTransaction}; + use crate::identity::SignedIdentity; - #[test] - fn transaction_versions() { - let transaction_v1 = - super::v1::ChronicleTransaction::new(Vec::default(), SignedIdentity::new_no_identity()); - let transaction_v2: super::v2::ChronicleTransaction = transaction_v1.clone().into(); - let transaction_current = transaction_v2.clone(); + #[test] + fn transaction_versions() { + let transaction_v1 = + super::v1::ChronicleTransaction::new(Vec::default(), SignedIdentity::new_no_identity()); + let transaction_v2: super::v2::ChronicleTransaction = transaction_v1.clone().into(); + let transaction_current = transaction_v2.clone(); - // check that the above sequence ends at the current version - assert_eq!(super::CURRENT_VERSION, transaction_current.get_version()); + // check that the above sequence ends at the current version + assert_eq!(super::CURRENT_VERSION, transaction_current.get_version()); - // check the reported version numbers - assert_eq!(1, transaction_v1.get_version()); - assert_eq!(2, transaction_v2.get_version()); + // check the reported version numbers + assert_eq!(1, transaction_v1.get_version()); + assert_eq!(2, transaction_v2.get_version()); - // check the conversions to current - assert_eq!(transaction_current, transaction_v1.to_current()); - assert_eq!(transaction_current, transaction_v2.to_current()); - } + // check the conversions to current + assert_eq!(transaction_current, transaction_v1.to_current()); + assert_eq!(transaction_current, transaction_v2.to_current()); + } } diff --git a/crates/common/src/prov/model/transaction/v1/mod.rs b/crates/common/src/prov/model/transaction/v1/mod.rs index 181be46c4..fc5be7345 100644 --- a/crates/common/src/prov/model/transaction/v1/mod.rs +++ b/crates/common/src/prov/model/transaction/v1/mod.rs @@ -1,13 +1,15 @@ use crate::{identity::SignedIdentity, prov::operations::ChronicleOperation}; +#[cfg(not(feature = "std"))] +use parity_scale_codec::alloc::vec::Vec; -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +#[derive(PartialEq, Eq, Debug, Clone)] pub struct ChronicleTransaction { - pub tx: Vec, - pub identity: SignedIdentity, + pub tx: Vec, + pub identity: SignedIdentity, } impl ChronicleTransaction { - pub fn new(tx: Vec, identity: SignedIdentity) -> Self { - Self { tx, identity } - } + pub fn new(tx: Vec, identity: SignedIdentity) -> Self { + Self { tx, identity } + } } diff --git a/crates/common/src/prov/model/transaction/v2/mod.rs b/crates/common/src/prov/model/transaction/v2/mod.rs index 181be46c4..fc5be7345 100644 --- a/crates/common/src/prov/model/transaction/v2/mod.rs +++ b/crates/common/src/prov/model/transaction/v2/mod.rs @@ -1,13 +1,15 @@ use crate::{identity::SignedIdentity, prov::operations::ChronicleOperation}; +#[cfg(not(feature = "std"))] +use parity_scale_codec::alloc::vec::Vec; -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +#[derive(PartialEq, Eq, Debug, Clone)] pub struct ChronicleTransaction { - pub tx: Vec, - pub identity: SignedIdentity, + pub tx: Vec, + pub identity: SignedIdentity, } impl ChronicleTransaction { - pub fn new(tx: Vec, identity: SignedIdentity) -> Self { - Self { tx, identity } - } + pub fn new(tx: Vec, identity: SignedIdentity) -> Self { + Self { tx, identity } + } } diff --git a/crates/common/src/prov/operations.rs b/crates/common/src/prov/operations.rs index 053a866a6..046973ddf 100644 --- a/crates/common/src/prov/operations.rs +++ b/crates/common/src/prov/operations.rs @@ -1,12 +1,18 @@ use chrono::{DateTime, NaiveDateTime, TimeZone, Utc}; + +#[cfg(feature = "diesel-bindings")] use diesel::{ - backend::Backend, - deserialize::FromSql, - serialize::{Output, ToSql}, - sql_types::Integer, - AsExpression, QueryId, SqlType, + self, + backend::Backend, + deserialize::FromSql, + serialize::{Output, ToSql}, + sql_types::Integer, + AsExpression, QueryId, SqlType, }; +#[cfg(not(feature = "std"))] +use parity_scale_codec::alloc::string::String; + use parity_scale_codec::{Decode, Encode, Error, Input}; use scale_info::{build::Fields, Path, Type, TypeInfo}; use uuid::Uuid; @@ -14,385 +20,366 @@ use uuid::Uuid; use crate::attributes::Attributes; use super::{ - ActivityId, AgentId, AssociationId, AttributionId, DelegationId, EntityId, ExternalId, - NamespaceId, Role, UuidWrapper, + ActivityId, AgentId, AssociationId, AttributionId, DelegationId, EntityId, ExternalId, + NamespaceId, Role, UuidWrapper, }; #[derive( - QueryId, - SqlType, - AsExpression, - Debug, - Copy, - Clone, - PartialEq, - Ord, - PartialOrd, - Eq, - Hash, - Serialize, - Deserialize, - Encode, - Decode, - TypeInfo, + Debug, + Copy, + Clone, + PartialEq, + Ord, + PartialOrd, + Eq, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + TypeInfo, )] -#[diesel(sql_type = Integer)] +#[cfg_attr(feature = "diesel-bindings", derive(AsExpression, SqlType, QueryId))] +#[cfg_attr(feature = "diesel-bindings", diesel(sql_type = Integer))] #[repr(i32)] pub enum DerivationType { - None, - Revision, - Quotation, - PrimarySource, -} - -impl ToSql for DerivationType -where - DB: Backend, - i32: ToSql, -{ - fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result { - match self { - DerivationType::None => (-1).to_sql(out), - DerivationType::Revision => 1.to_sql(out), - DerivationType::Quotation => 2.to_sql(out), - DerivationType::PrimarySource => 3.to_sql(out), - } - } -} - -impl FromSql for DerivationType -where - DB: Backend, - i32: FromSql, -{ - fn from_sql(bytes: ::RawValue<'_>) -> diesel::deserialize::Result { - match i32::from_sql(bytes)? { - -1 => Ok(DerivationType::None), - 1 => Ok(DerivationType::Revision), - 2 => Ok(DerivationType::Quotation), - 3 => Ok(DerivationType::PrimarySource), - _ => Err("Unrecognized enum variant".into()), - } - } + None, + Revision, + Quotation, + PrimarySource, +} + +#[cfg(feature = "diesel-bindings")] +mod bindings { + use super::*; + impl ToSql for DerivationType + where + DB: Backend, + i32: ToSql, + { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result { + match self { + DerivationType::None => (-1).to_sql(out), + DerivationType::Revision => 1.to_sql(out), + DerivationType::Quotation => 2.to_sql(out), + DerivationType::PrimarySource => 3.to_sql(out), + } + } + } + + impl FromSql for DerivationType + where + DB: Backend, + i32: FromSql, + { + fn from_sql(bytes: ::RawValue<'_>) -> diesel::deserialize::Result { + match i32::from_sql(bytes)? { + -1 => Ok(DerivationType::None), + 1 => Ok(DerivationType::Revision), + 2 => Ok(DerivationType::Quotation), + 3 => Ok(DerivationType::PrimarySource), + _ => Err("Unrecognized enum variant".into()), + } + } + } } impl TryFrom for DerivationType { - type Error = &'static str; - - fn try_from(value: i32) -> Result { - match value { - -1 => Ok(DerivationType::None), - 1 => Ok(DerivationType::Revision), - 2 => Ok(DerivationType::Quotation), - 3 => Ok(DerivationType::PrimarySource), - _ => Err("Unrecognized enum variant when converting from 'i32'"), - } - } + type Error = &'static str; + + fn try_from(value: i32) -> Result { + match value { + -1 => Ok(DerivationType::None), + 1 => Ok(DerivationType::Revision), + 2 => Ok(DerivationType::Quotation), + 3 => Ok(DerivationType::PrimarySource), + _ => Err("Unrecognized enum variant when converting from 'i32'"), + } + } } impl DerivationType { - pub fn revision() -> Self { - Self::Revision - } + pub fn revision() -> Self { + Self::Revision + } - pub fn quotation() -> Self { - Self::Quotation - } + pub fn quotation() -> Self { + Self::Quotation + } - pub fn primary_source() -> Self { - Self::PrimarySource - } + pub fn primary_source() -> Self { + Self::PrimarySource + } } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct CreateNamespace { - pub id: NamespaceId, - pub external_id: ExternalId, - pub uuid: UuidWrapper, + pub id: NamespaceId, + pub external_id: ExternalId, + pub uuid: UuidWrapper, } impl CreateNamespace { - pub fn new(id: NamespaceId, external_id: impl AsRef, uuid: Uuid) -> Self { - Self { - id, - external_id: external_id.as_ref().into(), - uuid: uuid.into(), - } - } + pub fn new(id: NamespaceId, external_id: impl AsRef, uuid: Uuid) -> Self { + Self { id, external_id: external_id.as_ref().into(), uuid: uuid.into() } + } } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct AgentExists { - pub namespace: NamespaceId, - pub external_id: ExternalId, + pub namespace: NamespaceId, + pub external_id: ExternalId, } impl AgentExists { - pub fn new(namespace: NamespaceId, external_id: impl AsRef) -> Self { - Self { - namespace, - external_id: external_id.as_ref().into(), - } - } + pub fn new(namespace: NamespaceId, external_id: impl AsRef) -> Self { + Self { namespace, external_id: external_id.as_ref().into() } + } } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct ActsOnBehalfOf { - pub id: DelegationId, - pub role: Option, - pub activity_id: Option, - pub responsible_id: AgentId, - pub delegate_id: AgentId, - pub namespace: NamespaceId, + pub id: DelegationId, + pub role: Option, + pub activity_id: Option, + pub responsible_id: AgentId, + pub delegate_id: AgentId, + pub namespace: NamespaceId, } impl ActsOnBehalfOf { - pub fn new( - namespace: &NamespaceId, - responsible_id: &AgentId, - delegate_id: &AgentId, - activity_id: Option<&ActivityId>, - role: Option, - ) -> Self { - Self { - namespace: namespace.clone(), - id: DelegationId::from_component_ids( - delegate_id, - responsible_id, - activity_id, - role.as_ref(), - ), - role, - activity_id: activity_id.cloned(), - responsible_id: responsible_id.clone(), - delegate_id: delegate_id.clone(), - } - } + pub fn new( + namespace: &NamespaceId, + responsible_id: &AgentId, + delegate_id: &AgentId, + activity_id: Option<&ActivityId>, + role: Option, + ) -> Self { + Self { + namespace: namespace.clone(), + id: DelegationId::from_component_ids( + delegate_id, + responsible_id, + activity_id, + role.as_ref(), + ), + role, + activity_id: activity_id.cloned(), + responsible_id: responsible_id.clone(), + delegate_id: delegate_id.clone(), + } + } } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct ActivityExists { - pub namespace: NamespaceId, - pub external_id: ExternalId, + pub namespace: NamespaceId, + pub external_id: ExternalId, } #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)] pub struct TimeWrapper(pub DateTime); impl TimeWrapper { - pub fn to_rfc3339(&self) -> String { - self.0.to_rfc3339() - } + pub fn to_rfc3339(&self) -> String { + self.0.to_rfc3339() + } } -impl std::fmt::Display for TimeWrapper { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0.to_rfc3339()) - } +impl core::fmt::Display for TimeWrapper { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0.to_rfc3339()) + } } impl From> for TimeWrapper { - fn from(dt: DateTime) -> Self { - TimeWrapper(dt) - } + fn from(dt: DateTime) -> Self { + TimeWrapper(dt) + } } impl Encode for TimeWrapper { - fn encode_to(&self, dest: &mut T) { - let timestamp = self.0.timestamp(); - let subsec_nanos = self.0.timestamp_subsec_nanos(); - (timestamp, subsec_nanos).encode_to(dest); - } + fn encode_to(&self, dest: &mut T) { + let timestamp = self.0.timestamp(); + let subsec_nanos = self.0.timestamp_subsec_nanos(); + (timestamp, subsec_nanos).encode_to(dest); + } } impl Decode for TimeWrapper { - fn decode(input: &mut I) -> Result { - let (timestamp, subsec_nanos) = <(i64, u32)>::decode(input)?; + fn decode(input: &mut I) -> Result { + let (timestamp, subsec_nanos) = <(i64, u32)>::decode(input)?; - let datetime = Utc.from_utc_datetime( - &NaiveDateTime::from_timestamp_opt(timestamp, subsec_nanos) - .ok_or("Invalid timestamp")?, - ); + let datetime = Utc.from_utc_datetime( + &NaiveDateTime::from_timestamp_opt(timestamp, subsec_nanos) + .ok_or("Invalid timestamp")?, + ); - Ok(Self(datetime)) - } + Ok(Self(datetime)) + } } impl TypeInfo for TimeWrapper { - type Identity = Self; - - fn type_info() -> Type { - Type::builder() - .path(Path::new("TimeWrapper", module_path!())) - .composite( - Fields::unnamed() - .field(|f| f.ty::().type_name("Timestamp")) - .field(|f| f.ty::().type_name("SubsecNanos")), - ) - } + type Identity = Self; + + fn type_info() -> Type { + Type::builder().path(Path::new("TimeWrapper", module_path!())).composite( + Fields::unnamed() + .field(|f| f.ty::().type_name("Timestamp")) + .field(|f| f.ty::().type_name("SubsecNanos")), + ) + } } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct StartActivity { - pub namespace: NamespaceId, - pub id: ActivityId, - pub time: TimeWrapper, + pub namespace: NamespaceId, + pub id: ActivityId, + pub time: TimeWrapper, } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct EndActivity { - pub namespace: NamespaceId, - pub id: ActivityId, - pub time: TimeWrapper, + pub namespace: NamespaceId, + pub id: ActivityId, + pub time: TimeWrapper, } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct ActivityUses { - pub namespace: NamespaceId, - pub id: EntityId, - pub activity: ActivityId, + pub namespace: NamespaceId, + pub id: EntityId, + pub activity: ActivityId, } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct EntityExists { - pub namespace: NamespaceId, - pub external_id: ExternalId, + pub namespace: NamespaceId, + pub external_id: ExternalId, } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct WasGeneratedBy { - pub namespace: NamespaceId, - pub id: EntityId, - pub activity: ActivityId, + pub namespace: NamespaceId, + pub id: EntityId, + pub activity: ActivityId, } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct EntityDerive { - pub namespace: NamespaceId, - pub id: EntityId, - pub used_id: EntityId, - pub activity_id: Option, - pub typ: DerivationType, + pub namespace: NamespaceId, + pub id: EntityId, + pub used_id: EntityId, + pub activity_id: Option, + pub typ: DerivationType, } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct WasAssociatedWith { - pub id: AssociationId, - pub role: Option, - pub namespace: NamespaceId, - pub activity_id: ActivityId, - pub agent_id: AgentId, + pub id: AssociationId, + pub role: Option, + pub namespace: NamespaceId, + pub activity_id: ActivityId, + pub agent_id: AgentId, } impl WasAssociatedWith { - pub fn new( - namespace: &NamespaceId, - activity_id: &ActivityId, - agent_id: &AgentId, - role: Option, - ) -> Self { - Self { - id: AssociationId::from_component_ids(agent_id, activity_id, role.as_ref()), - role, - namespace: namespace.clone(), - activity_id: activity_id.clone(), - agent_id: agent_id.clone(), - } - } + pub fn new( + namespace: &NamespaceId, + activity_id: &ActivityId, + agent_id: &AgentId, + role: Option, + ) -> Self { + Self { + id: AssociationId::from_component_ids(agent_id, activity_id, role.as_ref()), + role, + namespace: namespace.clone(), + activity_id: activity_id.clone(), + agent_id: agent_id.clone(), + } + } } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct WasAttributedTo { - pub id: AttributionId, - pub role: Option, - pub namespace: NamespaceId, - pub entity_id: EntityId, - pub agent_id: AgentId, + pub id: AttributionId, + pub role: Option, + pub namespace: NamespaceId, + pub entity_id: EntityId, + pub agent_id: AgentId, } impl WasAttributedTo { - pub fn new( - namespace: &NamespaceId, - entity_id: &EntityId, - agent_id: &AgentId, - role: Option, - ) -> Self { - Self { - id: AttributionId::from_component_ids(agent_id, entity_id, role.as_ref()), - role, - namespace: namespace.clone(), - entity_id: entity_id.clone(), - agent_id: agent_id.clone(), - } - } + pub fn new( + namespace: &NamespaceId, + entity_id: &EntityId, + agent_id: &AgentId, + role: Option, + ) -> Self { + Self { + id: AttributionId::from_component_ids(agent_id, entity_id, role.as_ref()), + role, + namespace: namespace.clone(), + entity_id: entity_id.clone(), + agent_id: agent_id.clone(), + } + } } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub struct WasInformedBy { - pub namespace: NamespaceId, - pub activity: ActivityId, - pub informing_activity: ActivityId, + pub namespace: NamespaceId, + pub activity: ActivityId, + pub informing_activity: ActivityId, } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub enum SetAttributes { - Entity { - namespace: NamespaceId, - id: EntityId, - attributes: Attributes, - }, - Agent { - namespace: NamespaceId, - id: AgentId, - attributes: Attributes, - }, - Activity { - namespace: NamespaceId, - id: ActivityId, - attributes: Attributes, - }, + Entity { namespace: NamespaceId, id: EntityId, attributes: Attributes }, + Agent { namespace: NamespaceId, id: AgentId, attributes: Attributes }, + Activity { namespace: NamespaceId, id: ActivityId, attributes: Attributes }, } #[derive(Serialize, Deserialize, Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)] pub enum ChronicleOperation { - CreateNamespace(CreateNamespace), - AgentExists(AgentExists), - AgentActsOnBehalfOf(ActsOnBehalfOf), - ActivityExists(ActivityExists), - StartActivity(StartActivity), - EndActivity(EndActivity), - ActivityUses(ActivityUses), - EntityExists(EntityExists), - WasGeneratedBy(WasGeneratedBy), - EntityDerive(EntityDerive), - SetAttributes(SetAttributes), - WasAssociatedWith(WasAssociatedWith), - WasAttributedTo(WasAttributedTo), - WasInformedBy(WasInformedBy), + CreateNamespace(CreateNamespace), + AgentExists(AgentExists), + AgentActsOnBehalfOf(ActsOnBehalfOf), + ActivityExists(ActivityExists), + StartActivity(StartActivity), + EndActivity(EndActivity), + ActivityUses(ActivityUses), + EntityExists(EntityExists), + WasGeneratedBy(WasGeneratedBy), + EntityDerive(EntityDerive), + SetAttributes(SetAttributes), + WasAssociatedWith(WasAssociatedWith), + WasAttributedTo(WasAttributedTo), + WasInformedBy(WasInformedBy), } impl ChronicleOperation { - /// Returns a reference to the `NamespaceId` of the `ChronicleOperation` - pub fn namespace(&self) -> &NamespaceId { - match self { - ChronicleOperation::ActivityExists(o) => &o.namespace, - ChronicleOperation::AgentExists(o) => &o.namespace, - ChronicleOperation::AgentActsOnBehalfOf(o) => &o.namespace, - ChronicleOperation::CreateNamespace(o) => &o.id, - ChronicleOperation::StartActivity(o) => &o.namespace, - ChronicleOperation::EndActivity(o) => &o.namespace, - ChronicleOperation::ActivityUses(o) => &o.namespace, - ChronicleOperation::EntityExists(o) => &o.namespace, - ChronicleOperation::WasGeneratedBy(o) => &o.namespace, - ChronicleOperation::EntityDerive(o) => &o.namespace, - ChronicleOperation::SetAttributes(o) => match o { - SetAttributes::Activity { namespace, .. } => namespace, - SetAttributes::Agent { namespace, .. } => namespace, - SetAttributes::Entity { namespace, .. } => namespace, - }, - ChronicleOperation::WasAssociatedWith(o) => &o.namespace, - ChronicleOperation::WasAttributedTo(o) => &o.namespace, - ChronicleOperation::WasInformedBy(o) => &o.namespace, - } - } + /// Returns a reference to the `NamespaceId` of the `ChronicleOperation` + pub fn namespace(&self) -> &NamespaceId { + match self { + ChronicleOperation::ActivityExists(o) => &o.namespace, + ChronicleOperation::AgentExists(o) => &o.namespace, + ChronicleOperation::AgentActsOnBehalfOf(o) => &o.namespace, + ChronicleOperation::CreateNamespace(o) => &o.id, + ChronicleOperation::StartActivity(o) => &o.namespace, + ChronicleOperation::EndActivity(o) => &o.namespace, + ChronicleOperation::ActivityUses(o) => &o.namespace, + ChronicleOperation::EntityExists(o) => &o.namespace, + ChronicleOperation::WasGeneratedBy(o) => &o.namespace, + ChronicleOperation::EntityDerive(o) => &o.namespace, + ChronicleOperation::SetAttributes(o) => match o { + SetAttributes::Activity { namespace, .. } => namespace, + SetAttributes::Agent { namespace, .. } => namespace, + SetAttributes::Entity { namespace, .. } => namespace, + }, + ChronicleOperation::WasAssociatedWith(o) => &o.namespace, + ChronicleOperation::WasAttributedTo(o) => &o.namespace, + ChronicleOperation::WasInformedBy(o) => &o.namespace, + } + } } diff --git a/crates/common/src/prov/vocab.rs b/crates/common/src/prov/vocab.rs index a1b827762..64228c57e 100644 --- a/crates/common/src/prov/vocab.rs +++ b/crates/common/src/prov/vocab.rs @@ -1,330 +1,444 @@ -use iref::IriBuf; -use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; -use uuid::Uuid; - -use super::{ActivityId, AgentId, EntityId, ExternalId, ExternalIdPart, Role}; - -#[derive(IriEnum, Clone, Copy, PartialEq, Eq, Hash)] -#[iri_prefix("chronicleop" = "http://btp.works/chronicleoperations/ns#")] -pub enum ChronicleOperations { - #[iri("chronicleop:CreateNamespace")] - CreateNamespace, - - #[iri("chronicleop:namespaceName")] - NamespaceName, - #[iri("chronicleop:namespaceUuid")] - NamespaceUuid, - #[iri("chronicleop:AgentExists")] - AgentExists, - #[iri("chronicleop:agentName")] - AgentName, - #[iri("chronicleop:agentUuid")] - AgentUuid, - #[iri("chronicleop:AgentActsOnBehalfOf")] - AgentActsOnBehalfOf, - #[iri("chronicleop:delegateId")] - DelegateId, - #[iri("chronicleop:responsibleId")] - ResponsibleId, - #[iri("chronicleop:RegisterKey")] - RegisterKey, - #[iri("chronicleop:publicKey")] - PublicKey, - #[iri("chronicleop:ActivityExists")] - ActivityExists, - #[iri("chronicleop:activityName")] - ActivityName, - #[iri("chronicleop:StartActivity")] - StartActivity, - #[iri("chronicleop:startActivityTime")] - StartActivityTime, - #[iri("chronicleop:EndActivity")] - EndActivity, - #[iri("chronicleop:endActivityTime")] - EndActivityTime, - #[iri("chronicleop:WasAssociatedWith")] - WasAssociatedWith, - #[iri("chronicleop:WasAttributedTo")] - WasAttributedTo, - #[iri("chronicleop:ActivityUses")] - ActivityUses, - #[iri("chronicleop:entityName")] - EntityName, - #[iri("chronicleop:locator")] - Locator, - #[iri("chronicleop:role")] - Role, - #[iri("chronicleop:EntityExists")] - EntityExists, - #[iri("chronicleop:WasGeneratedBy")] - WasGeneratedBy, - #[iri("chronicleop:EntityDerive")] - EntityDerive, - #[iri("chronicleop:derivationType")] - DerivationType, - #[iri("chronicleop:usedEntityName")] - UsedEntityName, - #[iri("chronicleop:SetAttributes")] - SetAttributes, - #[iri("chronicleop:attributes")] - Attributes, - #[iri("chronicleop:attribute")] - Attribute, - #[iri("chronicleop:domaintypeId")] - DomaintypeId, - #[iri("chronicleop:WasInformedBy")] - WasInformedBy, - #[iri("chronicleop:informingActivityName")] - InformingActivityName, - #[iri("chronicleop:Generated")] - Generated, -} +mod chronicle_operations { + use iri_string::types::{IriAbsoluteString, UriAbsoluteString}; + + #[derive(Clone, Copy, PartialEq, Eq, Hash)] + pub enum ChronicleOperations { + CreateNamespace, + + NamespaceName, + NamespaceUuid, + AgentExists, + AgentName, + AgentUuid, + AgentActsOnBehalfOf, + DelegateId, + ResponsibleId, + ActivityExists, + ActivityName, + StartActivity, + StartActivityTime, + EndActivity, + EndActivityTime, + WasAssociatedWith, + WasAttributedTo, + ActivityUses, + EntityName, + Locator, + Role, + EntityExists, + WasGeneratedBy, + EntityDerive, + DerivationType, + UsedEntityName, + SetAttributes, + Attributes, + Attribute, + DomaintypeId, + WasInformedBy, + InformingActivityName, + Generated, + } + + const ACTIVITY_EXISTS: &str = "http://btp.works/chronicleoperation/ns#ActivityExists"; + const ACTIVITY_NAME: &str = "http://btp.works/chronicleoperation/ns#ActivityName"; + const START_ACTIVITY: &str = "http://btp.works/chronicleoperation/ns#StartActivity"; + const START_ACTIVITY_TIME: &str = "http://btp.works/chronicleoperation/ns#StartActivityTime"; + const END_ACTIVITY: &str = "http://btp.works/chronicleoperation/ns#EndActivity"; + const END_ACTIVITY_TIME: &str = "http://btp.works/chronicleoperation/ns#EndActivityTime"; + const WAS_ASSOCIATED_WITH: &str = "http://btp.works/chronicleoperation/ns#WasAssociatedWith"; + const WAS_ATTRIBUTED_TO: &str = "http://btp.works/chronicleoperation/ns#WasAttributedTo"; + const ACTIVITY_USES: &str = "http://btp.works/chronicleoperation/ns#ActivityUses"; + const ENTITY_NAME: &str = "http://btp.works/chronicleoperation/ns#EntityName"; + const LOCATOR: &str = "http://btp.works/chronicleoperation/ns#Locator"; + const ROLE: &str = "http://btp.works/chronicleoperation/ns#Role"; + const ENTITY_EXISTS: &str = "http://btp.works/chronicleoperation/ns#EntityExists"; + const WAS_GENERATED_BY: &str = "http://btp.works/chronicleoperation/ns#WasGeneratedBy"; + const ENTITY_DERIVE: &str = "http://btp.works/chronicleoperation/ns#EntityDerive"; + const DERIVATION_TYPE: &str = "http://btp.works/chronicleoperation/ns#DerivationType"; + const USED_ENTITY_NAME: &str = "http://btp.works/chronicleoperation/ns#UsedEntityName"; + const SET_ATTRIBUTES: &str = "http://btp.works/chronicleoperation/ns#SetAttributes"; + const ATTRIBUTES: &str = "http://btp.works/chronicleoperation/ns#Attributes"; + const ATTRIBUTE: &str = "http://btp.works/chronicleoperation/ns#Attribute"; + const DOMAINTYPE_ID: &str = "http://btp.works/chronicleoperation/ns#DomaintypeId"; + const WAS_INFORMED_BY: &str = "http://btp.works/chronicleoperation/ns#WasInformedBy"; + const INFORMING_ACTIVITY_NAME: &str = + "http://btp.works/chronicleoperation/ns#InformingActivityName"; + const GENERATED: &str = "http://btp.works/chronicleoperation/ns#Generated"; + const CREATE_NAMESPACE: &str = "http://btp.works/chronicleoperation/ns#CreateNamespace"; + const NAMESPACE_NAME: &str = "http://btp.works/chronicleoperation/ns#namespaceName"; + const NAMESPACE_UUID: &str = "http://btp.works/chronicleoperation/ns#namespaceUuid"; + const AGENT_EXISTS: &str = "http://btp.works/chronicleoperation/ns#AgentExists"; + const AGENT_NAME: &str = "http://btp.works/chronicleoperation/ns#agentName"; + const AGENT_UUID: &str = "http://btp.works/chronicleoperation/ns#agentUuid"; + const AGENT_ACTS_ON_BEHALF_OF: &str = + "http://btp.works/chronicleoperation/ns#AgentActsOnBehalfOf"; + const DELEGATE_ID: &str = "http://btp.works/chronicleoperation/ns#delegateId"; + const RESPONSIBLE_ID: &str = "http://btp.works/chronicleoperation/ns#responsibleId"; + + impl AsRef for ChronicleOperations { + fn as_ref(&self) -> &'static str { + match self { + ChronicleOperations::ActivityExists => &ACTIVITY_EXISTS, + ChronicleOperations::ActivityName => &ACTIVITY_NAME, + ChronicleOperations::StartActivity => &START_ACTIVITY, + ChronicleOperations::StartActivityTime => &START_ACTIVITY_TIME, + ChronicleOperations::EndActivity => &END_ACTIVITY, + ChronicleOperations::EndActivityTime => &END_ACTIVITY_TIME, + ChronicleOperations::WasAssociatedWith => &WAS_ASSOCIATED_WITH, + ChronicleOperations::WasAttributedTo => &WAS_ATTRIBUTED_TO, + ChronicleOperations::ActivityUses => &ACTIVITY_USES, + ChronicleOperations::EntityName => &ENTITY_NAME, + ChronicleOperations::Locator => &LOCATOR, + ChronicleOperations::Role => &ROLE, + ChronicleOperations::EntityExists => &ENTITY_EXISTS, + ChronicleOperations::WasGeneratedBy => &WAS_GENERATED_BY, + ChronicleOperations::EntityDerive => &ENTITY_DERIVE, + ChronicleOperations::DerivationType => &DERIVATION_TYPE, + ChronicleOperations::UsedEntityName => &USED_ENTITY_NAME, + ChronicleOperations::SetAttributes => &SET_ATTRIBUTES, + ChronicleOperations::Attributes => &ATTRIBUTES, + ChronicleOperations::Attribute => &ATTRIBUTE, + ChronicleOperations::DomaintypeId => &DOMAINTYPE_ID, + ChronicleOperations::WasInformedBy => &WAS_INFORMED_BY, + ChronicleOperations::InformingActivityName => &INFORMING_ACTIVITY_NAME, + ChronicleOperations::Generated => &GENERATED, + ChronicleOperations::CreateNamespace => &CREATE_NAMESPACE, + ChronicleOperations::NamespaceName => &NAMESPACE_NAME, + ChronicleOperations::NamespaceUuid => &NAMESPACE_UUID, + ChronicleOperations::AgentExists => &AGENT_EXISTS, + ChronicleOperations::AgentName => &AGENT_NAME, + ChronicleOperations::AgentUuid => &AGENT_UUID, + ChronicleOperations::AgentActsOnBehalfOf => &AGENT_ACTS_ON_BEHALF_OF, + ChronicleOperations::DelegateId => &DELEGATE_ID, + ChronicleOperations::ResponsibleId => &RESPONSIBLE_ID, + } + } + } -#[derive(IriEnum, Clone, Copy, PartialEq, Eq, Hash)] -#[iri_prefix("prov" = "http://www.w3.org/ns/prov#")] -pub enum Prov { - #[iri("prov:Agent")] - Agent, - #[iri("prov:Entity")] - Entity, - #[iri("prov:Activity")] - Activity, - #[iri("prov:wasAssociatedWith")] - WasAssociatedWith, - #[iri("prov:qualifiedAssociation")] - QualifiedAssociation, - #[iri("prov:qualifiedAttribution")] - QualifiedAttribution, - #[iri("prov:Association")] - Association, - #[iri("prov:Attribution")] - Attribution, - #[iri("prov:agent")] - Responsible, - #[iri("prov:wasGeneratedBy")] - WasGeneratedBy, - #[iri("prov:used")] - Used, - #[iri("prov:wasAttributedTo")] - WasAttributedTo, - #[iri("prov:startedAtTime")] - StartedAtTime, - #[iri("prov:endedAtTime")] - EndedAtTime, - #[iri("prov:wasDerivedFrom")] - WasDerivedFrom, - #[iri("prov:hadPrimarySource")] - HadPrimarySource, - #[iri("prov:wasQuotedFrom")] - WasQuotedFrom, - #[iri("prov:wasRevisionOf")] - WasRevisionOf, - #[iri("prov:actedOnBehalfOf")] - ActedOnBehalfOf, - #[iri("prov:qualifiedDelegation")] - QualifiedDelegation, - #[iri("prov:Delegation")] - Delegation, - #[iri("prov:agent")] - Delegate, - #[iri("prov:hadRole")] - HadRole, - #[iri("prov:hadActivity")] - HadActivity, - #[iri("prov:hadEntity")] - HadEntity, - #[iri("prov:wasInformedBy")] - WasInformedBy, - #[iri("prov:generated")] - Generated, + #[cfg(feature = "json-ld")] + impl Into for ChronicleOperations { + fn into(self) -> IriAbsoluteString { + UriAbsoluteString::try_from(self.as_str().to_string()) + .unwrap() + .try_into() + .unwrap() + } + } + + impl ChronicleOperations { + pub fn as_str(&self) -> &str { + self.as_ref() + } + } + + impl core::fmt::Display for ChronicleOperations { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.as_str()) + } + } } -#[derive(IriEnum, Clone, Copy, PartialEq, Eq, Hash)] -#[iri_prefix("chronicle" = "http://btp.works/chronicle/ns#")] -pub enum Chronicle { - #[iri("chronicle:externalId")] - ExternalId, - #[iri("chronicle:Identity")] - Identity, - #[iri("chronicle:hasIdentity")] - HasIdentity, - #[iri("chronicle:hadIdentity")] - HadIdentity, - #[iri("chronicle:Namespace")] - Namespace, - #[iri("chronicle:hasNamespace")] - HasNamespace, - #[iri("chronicle:publicKey")] - PublicKey, - #[iri("chronicle:value")] - Value, - #[iri("chronicle:wasInformedBy")] - WasInformedBy, - #[iri("chronicle:generated")] - Generated, +pub use chronicle_operations::ChronicleOperations; + +mod prov { + use iri_string::types::{IriAbsoluteString, UriAbsoluteString}; + + use crate::prov::FromCompact; + + #[derive(Clone, Copy, PartialEq, Eq, Hash)] + pub enum Prov { + Agent, + Entity, + Activity, + WasAssociatedWith, + QualifiedAssociation, + QualifiedAttribution, + Association, + Attribution, + Responsible, + WasGeneratedBy, + Used, + WasAttributedTo, + StartedAtTime, + EndedAtTime, + WasDerivedFrom, + HadPrimarySource, + WasQuotedFrom, + WasRevisionOf, + ActedOnBehalfOf, + QualifiedDelegation, + Delegation, + Delegate, + HadRole, + HadActivity, + HadEntity, + WasInformedBy, + Generated, + } + + const AGENT: &str = "http://www.w3.org/ns/prov#Agent"; + const ENTITY: &str = "http://www.w3.org/ns/prov#Entity"; + const ACTIVITY: &str = "http://www.w3.org/ns/prov#Activity"; + const WAS_ASSOCIATED_WITH: &str = "http://www.w3.org/ns/prov#wasAssociatedWith"; + const QUALIFIED_ASSOCIATION: &str = "http://www.w3.org/ns/prov#qualifiedAssociation"; + const QUALIFIED_ATTRIBUTION: &str = "http://www.w3.org/ns/prov#qualifiedAttribution"; + const ASSOCIATION: &str = "http://www.w3.org/ns/prov#Association"; + const ATTRIBUTION: &str = "http://www.w3.org/ns/prov#Attribution"; + const RESPONSIBLE: &str = "http://www.w3.org/ns/prov#agent"; + const WAS_GENERATED_BY: &str = "http://www.w3.org/ns/prov#wasGeneratedBy"; + const USED: &str = "http://www.w3.org/ns/prov#used"; + const WAS_ATTRIBUTED_TO: &str = "http://www.w3.org/ns/prov#wasAttributedTo"; + const STARTED_AT_TIME: &str = "http://www.w3.org/ns/prov#startedAtTime"; + const ENDED_AT_TIME: &str = "http://www.w3.org/ns/prov#endedAtTime"; + const WAS_DERIVED_FROM: &str = "http://www.w3.org/ns/prov#wasDerivedFrom"; + const HAD_PRIMARY_SOURCE: &str = "http://www.w3.org/ns/prov#hadPrimarySource"; + const WAS_QUOTED_FROM: &str = "http://www.w3.org/ns/prov#wasQuotedFrom"; + const WAS_REVISION_OF: &str = "http://www.w3.org/ns/prov#wasRevisionOf"; + const ACTED_ON_BEHALF_OF: &str = "http://www.w3.org/ns/prov#actedOnBehalfOf"; + const QUALIFIED_DELEGATION: &str = "http://www.w3.org/ns/prov#qualifiedDelegation"; + const DELEGATION: &str = "http://www.w3.org/ns/prov#Delegation"; + const DELEGATE: &str = "http://www.w3.org/ns/prov#agent"; + const HAD_ROLE: &str = "http://www.w3.org/ns/prov#hadRole"; + const HAD_ACTIVITY: &str = "http://www.w3.org/ns/prov#hadActivity"; + const HAD_ENTITY: &str = "http://www.w3.org/ns/prov#hadEntity"; + const WAS_INFORMED_BY: &str = "http://www.w3.org/ns/prov#wasInformedBy"; + const GENERATED: &str = "http://www.w3.org/ns/prov#generated"; + + impl AsRef for Prov { + fn as_ref(&self) -> &'static str { + match self { + Prov::Agent => &AGENT, + Prov::Entity => &ENTITY, + Prov::Activity => &ACTIVITY, + Prov::WasAssociatedWith => &WAS_ASSOCIATED_WITH, + Prov::QualifiedAssociation => &QUALIFIED_ASSOCIATION, + Prov::QualifiedAttribution => &QUALIFIED_ATTRIBUTION, + Prov::Association => &ASSOCIATION, + Prov::Attribution => &ATTRIBUTION, + Prov::Responsible => &RESPONSIBLE, + Prov::WasGeneratedBy => &WAS_GENERATED_BY, + Prov::Used => &USED, + Prov::WasAttributedTo => &WAS_ATTRIBUTED_TO, + Prov::StartedAtTime => &STARTED_AT_TIME, + Prov::EndedAtTime => &ENDED_AT_TIME, + Prov::WasDerivedFrom => &WAS_DERIVED_FROM, + Prov::HadPrimarySource => &HAD_PRIMARY_SOURCE, + Prov::WasQuotedFrom => &WAS_QUOTED_FROM, + Prov::WasRevisionOf => &WAS_REVISION_OF, + Prov::ActedOnBehalfOf => &ACTED_ON_BEHALF_OF, + Prov::QualifiedDelegation => &QUALIFIED_DELEGATION, + Prov::Delegation => &DELEGATION, + Prov::Delegate => &DELEGATE, + Prov::HadRole => &HAD_ROLE, + Prov::HadActivity => &HAD_ACTIVITY, + Prov::HadEntity => &HAD_ENTITY, + Prov::WasInformedBy => &WAS_INFORMED_BY, + Prov::Generated => &GENERATED, + } + } + } + + #[cfg(feature = "json-ld")] + impl Into for Prov { + fn into(self) -> IriAbsoluteString { + UriAbsoluteString::try_from(self.as_str().to_string()) + .unwrap() + .try_into() + .unwrap() + } + } + + impl Prov { + pub fn as_str(&self) -> &str { + self.as_ref() + } + } + + impl core::fmt::Display for Prov { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.as_str()) + } + } } -/// Operations to format specific Iri kinds, using percentage encoding to ensure they are infallible -impl Chronicle { - pub const LONG_PREFIX: &'static str = "http://btp.works/chronicle/ns#"; - pub const PREFIX: &'static str = "chronicle:"; - - fn encode(s: &str) -> String { - percent_encode(s.as_bytes(), NON_ALPHANUMERIC).to_string() - } - - pub fn namespace(external_id: &ExternalId, id: &Uuid) -> IriBuf { - IriBuf::new(&format!( - "{}ns:{}:{}", - Self::PREFIX, - Self::encode(external_id.as_str()), - id - )) - .unwrap() - } - - pub fn agent(external_id: &ExternalId) -> IriBuf { - IriBuf::new(&format!( - "{}agent:{}", - Self::PREFIX, - Self::encode(external_id.as_str()) - )) - .unwrap() - } - - pub fn activity(external_id: &ExternalId) -> IriBuf { - IriBuf::new(&format!( - "{}activity:{}", - Self::PREFIX, - Self::encode(external_id.as_str()) - )) - .unwrap() - } - - pub fn entity(external_id: &ExternalId) -> IriBuf { - IriBuf::new(&format!( - "{}entity:{}", - Self::PREFIX, - Self::encode(external_id.as_str()) - )) - .unwrap() - } - - pub fn domaintype(external_id: &ExternalId) -> IriBuf { - IriBuf::new(&format!( - "{}domaintype:{}", - Self::PREFIX, - Self::encode(external_id.as_str()) - )) - .unwrap() - } - - pub fn identity(agent_name: &ExternalId, public_key: impl AsRef) -> IriBuf { - IriBuf::new(&format!( - "{}identity:{}:{}", - Self::PREFIX, - Self::encode(agent_name.as_str()), - Self::encode(public_key.as_ref()) - )) - .unwrap() - } - - pub fn association(agent: &AgentId, activity: &ActivityId, role: &Option) -> IriBuf { - IriBuf::new(&format!( - "{}association:{}:{}:role={}", - Self::PREFIX, - Self::encode(agent.external_id_part().as_str()), - Self::encode(activity.external_id_part().as_ref()), - Self::encode( - &role - .as_ref() - .map(|x| x.to_string()) - .unwrap_or_else(|| "".to_owned()) - ), - )) - .unwrap() - } - - pub fn delegation( - delegate: &AgentId, - responsible: &AgentId, - activity: &Option, - role: &Option, - ) -> IriBuf { - IriBuf::new(&format!( - "{}delegation:{}:{}:role={}:activity={}", - Self::PREFIX, - Self::encode(delegate.external_id_part().as_str()), - Self::encode(responsible.external_id_part().as_str()), - Self::encode(role.as_ref().map(|x| x.as_str()).unwrap_or("")), - Self::encode( - activity - .as_ref() - .map(|x| x.external_id_part().as_str()) - .unwrap_or("") - ), - )) - .unwrap() - } - - pub fn attribution(agent: &AgentId, entity: &EntityId, role: &Option) -> IriBuf { - IriBuf::new(&format!( - "{}attribution:{}:{}:role={}", - Self::PREFIX, - Self::encode(agent.external_id_part().as_str()), - Self::encode(entity.external_id_part().as_ref()), - Self::encode( - &role - .as_ref() - .map(|x| x.to_string()) - .unwrap_or_else(|| "".to_owned()) - ), - )) - .unwrap() - } +pub use prov::Prov; + +mod chronicle { + use iri_string::types::{IriAbsoluteString, UriAbsoluteString}; + #[cfg(not(feature = "std"))] + use parity_scale_codec::alloc::string::String; + #[cfg(not(feature = "std"))] + use scale_info::prelude::{borrow::ToOwned, string::ToString, *}; + use uuid::Uuid; + + use crate::prov::{ActivityId, AgentId, EntityId, ExternalId, ExternalIdPart, Role}; + + #[derive(Clone, Copy, PartialEq, Eq, Hash)] + pub enum Chronicle { + Namespace, + HasNamespace, + Value, + } + + const NAMESPACE: &str = "chronicle:namespace"; + const HAS_NAMESPACE: &str = "chronicle:hasNamespace"; + const VALUE: &str = "chronicle:value"; + + impl AsRef for Chronicle { + fn as_ref(&self) -> &'static str { + match self { + Chronicle::Namespace => &NAMESPACE, + Chronicle::HasNamespace => &HAS_NAMESPACE, + Chronicle::Value => &VALUE, + } + } + } + + #[cfg(feature = "json-ld")] + impl Into for Chronicle { + fn into(self) -> IriAbsoluteString { + UriAbsoluteString::try_from(self.as_str().to_string()) + .unwrap() + .try_into() + .unwrap() + } + } + + impl Chronicle { + pub fn as_str(&self) -> &str { + self.as_ref() + } + } + + impl core::fmt::Display for Chronicle { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.as_str()) + } + } + + /// Operations to format specific Iri kinds, using percentage encoding to ensure they are infallible + impl Chronicle { + pub const LONG_PREFIX: &'static str = "http://btp.works/chronicle/ns#"; + pub const PREFIX: &'static str = "chronicle:"; + + pub fn namespace(external_id: &ExternalId, id: &Uuid) -> UriAbsoluteString { + format!("{}ns:{}:{}", Self::PREFIX, external_id.as_str(), id) + .try_into() + .unwrap() + } + + pub fn agent(external_id: &ExternalId) -> UriAbsoluteString { + format!("{}agent:{}", Self::PREFIX, external_id.as_str()).try_into().unwrap() + } + + pub fn activity(external_id: &ExternalId) -> UriAbsoluteString { + format!("{}activity:{}", Self::PREFIX, external_id.as_str()).try_into().unwrap() + } + + pub fn entity(external_id: &ExternalId) -> UriAbsoluteString { + format!("{}entity:{}", Self::PREFIX, external_id.as_str()).try_into().unwrap() + } + + pub fn domaintype(external_id: &ExternalId) -> UriAbsoluteString { + format!("{}domaintype:{}", Self::PREFIX, external_id.as_str()) + .try_into() + .unwrap() + } + + pub fn association( + agent: &AgentId, + activity: &ActivityId, + role: &Option, + ) -> UriAbsoluteString { + format!( + "{}association:{}:{}:role={}", + Self::PREFIX, + agent.external_id_part().as_str(), + activity.external_id_part().as_ref(), + (&role.as_ref().map(|x| x.as_str()).unwrap_or_else(|| "")), + ) + .try_into() + .unwrap() + } + + pub fn delegation( + delegate: &AgentId, + responsible: &AgentId, + activity: &Option, + role: &Option, + ) -> UriAbsoluteString { + format!( + "{}delegation:{}:{}:role={}:activity={}", + Self::PREFIX, + delegate.external_id_part().as_str(), + responsible.external_id_part().as_str(), + role.as_ref().map(|x| x.as_str()).unwrap_or(""), + activity.as_ref().map(|x| x.external_id_part().as_str()).unwrap_or(""), + ) + .try_into() + .unwrap() + } + + pub fn attribution( + agent: &AgentId, + entity: &EntityId, + role: &Option, + ) -> UriAbsoluteString { + format!( + "{}attribution:{}:{}:role={}", + Self::PREFIX, + agent.external_id_part().as_str(), + entity.external_id_part().as_ref(), + (&role.as_ref().map(|x| x.to_string()).unwrap_or_else(|| "".to_owned())), + ) + .try_into() + .unwrap() + } + } } +pub use chronicle::*; + /// As these operations are meant to be infallible, prop test them to ensure #[cfg(test)] #[allow(clippy::useless_conversion)] mod test { - use crate::prov::{ActivityId, AgentId, EntityId, ExternalId, NamespaceId}; - - use super::Chronicle; - use iref::IriBuf; - use proptest::prelude::*; - use uuid::Uuid; - - proptest! { - #![proptest_config(ProptestConfig { - max_shrink_iters: std::u32::MAX, verbose: 0, .. ProptestConfig::default() - })] - #[test] - fn namespace(external_id in ".*") { - NamespaceId::try_from( - IriBuf::from(Chronicle::namespace(&ExternalId::from(external_id), &Uuid::new_v4())).as_iri() - ).unwrap(); - } - - #[test] - fn agent(external_id in ".*") { - AgentId::try_from( - IriBuf::from(Chronicle::agent(&ExternalId::from(external_id))).as_iri() - ).unwrap(); - } - - #[test] - fn entity(external_id in ".*") { - EntityId::try_from( - IriBuf::from(Chronicle::entity(&ExternalId::from(external_id))).as_iri() - ).unwrap(); - } - - #[test] - fn activity(external_id in ".*") { - ActivityId::try_from( - IriBuf::from(Chronicle::activity(&ExternalId::from(external_id))).as_iri() - ).unwrap(); - } - } + use crate::prov::{ActivityId, AgentId, EntityId, ExternalId, NamespaceId}; + + use super::Chronicle; + use proptest::prelude::*; + use uuid::Uuid; + + proptest! { + #![proptest_config(ProptestConfig { + max_shrink_iters: std::u32::MAX, verbose: 0, .. ProptestConfig::default() + })] + #[test] + fn namespace(external_id in ".*") { + NamespaceId::try_from( + Chronicle::namespace(&ExternalId::from(external_id), &Uuid::new_v4()) + ).unwrap(); + } + + #[test] + fn agent(external_id in ".*") { + AgentId::try_from( + Chronicle::agent(&ExternalId::from(external_id)) + ).unwrap(); + } + + #[test] + fn entity(external_id in ".*") { + EntityId::try_from( + Chronicle::entity(&ExternalId::from(external_id)) + ).unwrap(); + } + + #[test] + fn activity(external_id in ".*") { + ActivityId::try_from( + Chronicle::activity(&ExternalId::from(external_id)) + ).unwrap(); + } + } } diff --git a/crates/frame-chronicle/Cargo.toml b/crates/frame-chronicle/Cargo.toml deleted file mode 100644 index ff912e9f7..000000000 --- a/crates/frame-chronicle/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "frame-chronicle" -edition = "2021" -version = "0.7.5" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -common = {path = "../common"} -parity-scale-codec = {workspace = true} -sp-std = {workspace = true} -scale-info = {workspace = true} -frame-support = {workspace = true} -frame-system = {workspace = true} -frame-benchmarking = {workspace = true} -tracing = {workspace=true} -newtype-derive-2018 = {workspace=true} -macro-attr-2018 = {workspace=true} - -[dev-dependencies] -sp-runtime = {workspace = true} -sp-io = {workspace = true} -sp-core = {workspace = true} -uuid = {workspace = true} -chronicle-telemetry = {path="../chronicle-telemetry"} - -[features] -default = ["std"] -std = [ - "parity-scale-codec/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", - "scale-info/std", -] -runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] -try-runtime = ["frame-support/try-runtime"] diff --git a/crates/frame-chronicle/src/lib.rs b/crates/frame-chronicle/src/lib.rs deleted file mode 100644 index 40f04293a..000000000 --- a/crates/frame-chronicle/src/lib.rs +++ /dev/null @@ -1,186 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -use common::ledger::{LedgerAddress, ProvSnapshot}; -/// Edit this file to define custom logic or remove it if it is not needed. -/// Learn more about FRAME and the core library of Substrate FRAME pallets: -/// -pub use pallet::*; - -#[cfg(test)] -mod mock; - -#[cfg(test)] -mod tests; - -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking; -pub mod weights; -use parity_scale_codec::MaxEncodedLen; -use scale_info::TypeInfo; -pub use weights::*; - -#[frame_support::pallet] -pub mod pallet { - use super::*; - use common::ledger::StateInput; - use frame_support::pallet_prelude::{OptionQuery, *}; - use frame_system::pallet_prelude::*; - - #[pallet::pallet] - pub struct Pallet(_); - - /// Configure the pallet by specifying the parameters and types on which it depends. - #[pallet::config] - pub trait Config: frame_system::Config { - /// Because this pallet emits events, it depends on the runtime's definition of an event. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Type representing the weight of this pallet - type WeightInfo: WeightInfo; - - type OperationList: Parameter - + Into> - + From> - + parity_scale_codec::Codec; - } - // The pallet's runtime storage items. - // https://docs.substrate.io/main-docs/build/runtime-storage/ - #[pallet::storage] - #[pallet::getter(fn prov)] - // Learn more about declaring storage items: - // https://docs.substrate.io/main-docs/build/runtime-storage/#declaring-storage-items - pub type Provenance = StorageMap<_, Twox128, LedgerAddress, common::prov::ProvModel>; - - // Pallets use events to inform users when important changes are made. - // https://docs.substrate.io/main-docs/build/events-errors/ - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - Applied(common::prov::ProvModel), - Contradiction(common::prov::Contradiction), - } - - // Errors inform users that something went wrong. - #[pallet::error] - pub enum Error { - Address, - Contradiction, - Compaction, - Expansion, - Identity, - IRef, - NotAChronicleIri, - MissingId, - MissingProperty, - NotANode, - NotAnObject, - OpaExecutor, - SerdeJson, - SubmissionFormat, - Time, - Tokio, - Utf8, - } - - impl From for Error { - fn from(error: common::prov::ProcessorError) -> Self { - match error { - common::prov::ProcessorError::Address => Error::Address, - common::prov::ProcessorError::Compaction(_) => Error::Compaction, - common::prov::ProcessorError::Contradiction { .. } => Error::Contradiction, - common::prov::ProcessorError::Expansion { .. } => Error::Expansion, - common::prov::ProcessorError::Identity(_) => Error::Identity, - common::prov::ProcessorError::IRef(_) => Error::IRef, - common::prov::ProcessorError::NotAChronicleIri { .. } => Error::NotAChronicleIri, - common::prov::ProcessorError::MissingId { .. } => Error::MissingId, - common::prov::ProcessorError::MissingProperty { .. } => Error::MissingProperty, - common::prov::ProcessorError::NotANode(_) => Error::NotANode, - common::prov::ProcessorError::NotAnObject => Error::NotAnObject, - common::prov::ProcessorError::OpaExecutor(_) => Error::OpaExecutor, - common::prov::ProcessorError::SerdeJson(_) => Error::SerdeJson, - common::prov::ProcessorError::SubmissionFormat(_) => Error::SubmissionFormat, - common::prov::ProcessorError::Time(_) => Error::Time, - common::prov::ProcessorError::Tokio(_) => Error::Tokio, - common::prov::ProcessorError::Utf8(_) => Error::Utf8, - } - } - } - - // Dispatchable functions allows users to interact with the pallet and invoke state changes. - // These functions materialize as "extrinsics", which are often compared to transactions. - // Dispatchable functions must be annotated with a weight and must return a DispatchResult. - #[pallet::call] - impl Pallet { - /// An example dispatchable that takes a singles value as a parameter, writes the value to - /// storage and emits an event. This function must be dispatched by a signed extrinsic. - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::apply())] - pub fn apply(origin: OriginFor, operations: T::OperationList) -> DispatchResult { - // Check that the extrinsic was signed and get the signer. - // This function will return an error if the extrinsic is not signed. - // https://docs.substrate.io/main-docs/build/origins/ - let who = ensure_signed(origin)?; - - // Get operations and load their dependencies - let ops: Vec = operations.into(); - - let deps = ops - .iter() - .flat_map(|tx| tx.dependencies()) - .collect::>(); - - let initial_input_models: Vec<_> = deps - .into_iter() - .map(|addr| (addr.clone(), Provenance::::get(&addr))) - .collect(); - - let mut state: common::ledger::OperationState = common::ledger::OperationState::new(); - - state.update_state(initial_input_models.into_iter()); - - let mut model = common::prov::ProvModel::default(); - - for op in ops { - let res = op.process(model, state.input()); - match res { - // A contradiction raises an event, not an error and shortcuts processing - contradiction attempts are useful provenance - // and should not be a purely operational concern - Err(common::prov::ProcessorError::Contradiction(source)) => { - tracing::info!(contradiction = %source); - - Self::deposit_event(Event::::Contradiction(source)); - - return Ok(()); - } - // Severe errors should be logged - Err(e) => { - tracing::error!(chronicle_prov_failure = %e); - - return Err(Error::::from(e).into()); - } - Ok((tx_output, updated_model)) => { - state.update_state_from_output(tx_output.into_iter()); - model = updated_model; - } - } - } - - // Compute delta - let dirty = state.dirty().collect::>(); - - tracing::trace!(dirty = ?dirty); - - let mut delta = common::prov::ProvModel::default(); - for common::ledger::StateOutput { address, data } in dirty { - delta.combine(&data); - - // Update storage. - Provenance::::set(&address, Some(data)); - } - - // Emit an event. - Self::deposit_event(Event::Applied(delta)); - // Return a successful DispatchResultWithPostInfo - Ok(()) - } - } -} diff --git a/crates/frame-chronicle/src/tests.rs b/crates/frame-chronicle/src/tests.rs deleted file mode 100644 index b9ae391a1..000000000 --- a/crates/frame-chronicle/src/tests.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::{mock::*, Error, Event}; -use common::prov::{ - operations::{ChronicleOperation, CreateNamespace}, - ExternalId, NamespaceId, -}; -use frame_support::{assert_noop, assert_ok}; -use uuid::Uuid; - -#[test] -fn it_works_for_default_value() { - new_test_ext().execute_with(|| { - // Go past genesis block so events get deposited - System::set_block_number(1); - // Dispatch a signed extrinsic. - assert_ok!(ChronicleModule::apply(RuntimeOrigin::signed(1), vec![])); - // Assert that the correct event was deposited - System::assert_last_event(Event::Applied(common::prov::ProvModel::default()).into()); - }); -} - -#[test] -fn single_operation() { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - new_test_ext().execute_with(|| { - // Go past genesis block so events get deposited - System::set_block_number(1); - let uuid = Uuid::new_v4(); - let op = ChronicleOperation::CreateNamespace(CreateNamespace { - id: NamespaceId::from_external_id("test", uuid), - external_id: ExternalId::from("test"), - uuid: uuid.into(), - }); - // Dispatch our operation - assert_ok!(ChronicleModule::apply( - RuntimeOrigin::signed(1), - vec![op.clone()] - )); - - // Apply that operation to a new prov model for assertion - - // the pallet execution should produce an identical delta - let mut delta_model = common::prov::ProvModel::default(); - delta_model.apply(&op).unwrap(); - // Assert that the delta is correct - System::assert_last_event(Event::Applied(delta_model).into()); - }); -} diff --git a/crates/gq-subscribe/src/main.rs b/crates/gq-subscribe/src/main.rs index 03330ef76..910b28399 100644 --- a/crates/gq-subscribe/src/main.rs +++ b/crates/gq-subscribe/src/main.rs @@ -6,126 +6,117 @@ use std::net::{SocketAddr, ToSocketAddrs}; use tungstenite::{client::IntoClientRequest, connect, Message}; fn main() -> Result<(), anyhow::Error> { - let args = Command::new("gq-ws") - .author("Blockchain Technology Partners") - .about("Perform GraphQL subscription to a websocket") - .arg( - Arg::new("request") - .long("subscription") - .short('s') - .takes_value(true) - .required(true) - .help("the GraphQL subscription request"), - ) - .arg( - Arg::new("count") - .long("notification-count") - .short('c') - .takes_value(true) - .required(true) - .help("how many responses to report"), - ) - .arg( - Arg::new("address") - .long("chronicle-address") - .short('a') - .takes_value(true) - .default_value("localhost:9982") - .help("the network address of the Chronicle API"), - ) - .arg( - Arg::new("token") - .long("bearer-token") - .short('t') - .takes_value(true) - .help("the bearer token to pass for authorization"), - ) - .get_matches(); + let args = Command::new("gq-ws") + .author("Blockchain Technology Partners") + .about("Perform GraphQL subscription to a websocket") + .arg( + Arg::new("request") + .long("subscription") + .short('s') + .takes_value(true) + .required(true) + .help("the GraphQL subscription request"), + ) + .arg( + Arg::new("count") + .long("notification-count") + .short('c') + .takes_value(true) + .required(true) + .help("how many responses to report"), + ) + .arg( + Arg::new("address") + .long("chronicle-address") + .short('a') + .takes_value(true) + .default_value("localhost:9982") + .help("the network address of the Chronicle API"), + ) + .arg( + Arg::new("token") + .long("bearer-token") + .short('t') + .takes_value(true) + .help("the bearer token to pass for authorization"), + ) + .get_matches(); - let subscription_query = args.value_of("request").unwrap(); - let notification_count: u32 = args.value_of("count").unwrap().parse()?; - let chronicle_address: SocketAddr = args - .value_of("address") - .unwrap() - .to_socket_addrs()? - .next() - .expect("network address required for Chronicle API"); - let bearer_token = args.value_of("token"); + let subscription_query = args.value_of("request").unwrap(); + let notification_count: u32 = args.value_of("count").unwrap().parse()?; + let chronicle_address: SocketAddr = args + .value_of("address") + .unwrap() + .to_socket_addrs()? + .next() + .expect("network address required for Chronicle API"); + let bearer_token = args.value_of("token"); - // generate random ID for subscription - let subscription_id: String = thread_rng() - .sample_iter(&Alphanumeric) - .take(12) - .map(char::from) - .collect(); + // generate random ID for subscription + let subscription_id: String = + thread_rng().sample_iter(&Alphanumeric).take(12).map(char::from).collect(); - // prepare websocket request - let mut client_request = format!("ws://{chronicle_address}/ws").into_client_request()?; - let headers = client_request.headers_mut(); - if let Some(token) = bearer_token { - headers.insert( - "Authorization", - HeaderValue::from_str(&format!("Bearer {token}"))?, - ); - } - headers.insert( - "Sec-WebSocket-Protocol", - HeaderValue::from_str("graphql-ws")?, - ); + // prepare websocket request + let mut client_request = format!("ws://{chronicle_address}/ws").into_client_request()?; + let headers = client_request.headers_mut(); + if let Some(token) = bearer_token { + headers.insert("Authorization", HeaderValue::from_str(&format!("Bearer {token}"))?); + } + headers.insert("Sec-WebSocket-Protocol", HeaderValue::from_str("graphql-ws")?); - // connect and upgrade websocket - let (mut socket, response) = connect(client_request)?; - if response.status() != StatusCode::SWITCHING_PROTOCOLS { - panic!("failed connect and upgrade: {response:#?}"); - } + // connect and upgrade websocket + let (mut socket, response) = connect(client_request)?; + if response.status() != StatusCode::SWITCHING_PROTOCOLS { + panic!("failed connect and upgrade: {response:#?}"); + } - // initialize gql connection - let conn_init_json = json!({ - "type": "connection_init" - }); - let conn_init_msg = Message::Text(serde_json::to_string(&conn_init_json)?); - socket.send(conn_init_msg)?; - let conn_response = socket.read()?; - if let Value::Object(map) = serde_json::from_str::(&conn_response.clone().into_text()?)? - { - if map.get("type") == Some(&Value::String("connection_ack".to_string())) { - // connection initialized, so subscribe - let subscription_json = json!({ - "type": "start", - "id": subscription_id, - "payload": { - "query": subscription_query - } - }); - let subscription_msg = Message::Text(serde_json::to_string(&subscription_json)?); - socket.send(subscription_msg)?; + // initialize gql connection + let conn_init_json = json!({ + "type": "connection_init" + }); + let conn_init_msg = Message::Text(serde_json::to_string(&conn_init_json)?); + socket.send(conn_init_msg)?; + let conn_response = socket.read()?; + if let Value::Object(map) = serde_json::from_str::(&conn_response.clone().into_text()?)? + { + if map.get("type") == Some(&Value::String("connection_ack".to_string())) { + // connection initialized, so subscribe + let subscription_json = json!({ + "type": "start", + "id": subscription_id, + "payload": { + "query": subscription_query + } + }); + let subscription_msg = Message::Text(serde_json::to_string(&subscription_json)?); + socket.send(subscription_msg)?; - // receive and print notifications - let data_json = Value::String("data".to_string()); - let subscription_id_json = Value::String(subscription_id); - let mut remaining = notification_count; - while remaining > 0 { - remaining -= 1; - let notification_msg = socket.read()?; - let notification_json = - serde_json::from_str::(¬ification_msg.into_text()?)?; + // receive and print notifications + let data_json = Value::String("data".to_string()); + let subscription_id_json = Value::String(subscription_id); + let mut remaining = notification_count; + while remaining > 0 { + remaining -= 1; + let notification_msg = socket.read()?; + let notification_json = + serde_json::from_str::(¬ification_msg.into_text()?)?; - if let Value::Object(map) = notification_json.clone() { - if map.get("type") == Some(&data_json) - && map.get("id") == Some(&subscription_id_json) - { - let notification_pretty = - serde_json::to_string_pretty(map.get("payload").unwrap())?; - println!("{notification_pretty}"); - } else { - panic!("expected a response to subscription, got: {notification_json}"); - } - } else { - panic!("expected a JSON object notification, got: {notification_json}"); - } - } - return Ok(()); - } - } - panic!("expected acknowledgement of connection initialization, got: {conn_response}"); + if let Value::Object(map) = notification_json.clone() { + if map.get("type") == Some(&data_json) && + map.get("id") == Some(&subscription_id_json) + { + let notification_pretty = + serde_json::to_string_pretty(map.get("payload").unwrap())?; + println!("{notification_pretty}"); + } else { + panic!("expected a response to subscription, got: {notification_json}"); + } + } else { + panic!("expected a JSON object notification, got: {notification_json}"); + } + } + return Ok(()) + } + } + panic!("expected acknowledgement of connection initialization, got: {conn_response}"); } diff --git a/crates/id-provider/src/main.rs b/crates/id-provider/src/main.rs index 5b8509155..89c6dab86 100644 --- a/crates/id-provider/src/main.rs +++ b/crates/id-provider/src/main.rs @@ -1,68 +1,68 @@ use oauth2::{ - basic::BasicClient, reqwest::http_client, AuthUrl, AuthorizationCode, ClientId, ClientSecret, - CsrfToken, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, TokenUrl, + basic::BasicClient, reqwest::http_client, AuthUrl, AuthorizationCode, ClientId, ClientSecret, + CsrfToken, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, TokenUrl, }; use std::process::Command; use url::Url; fn main() -> Result<(), anyhow::Error> { - // construct OAuth query: authorization code flow with PKCE + // construct OAuth query: authorization code flow with PKCE - let oauth_client = BasicClient::new( - ClientId::new("client-id".to_string()), - Some(ClientSecret::new("client-secret".to_string())), - AuthUrl::new("http://localhost:8090/authorize".to_string())?, - Some(TokenUrl::new("http://localhost:8090/token".to_string())?), - ) - .set_redirect_uri(RedirectUrl::new("http://example.com/callback".to_string())?); + let oauth_client = BasicClient::new( + ClientId::new("client-id".to_string()), + Some(ClientSecret::new("client-secret".to_string())), + AuthUrl::new("http://localhost:8090/authorize".to_string())?, + Some(TokenUrl::new("http://localhost:8090/token".to_string())?), + ) + .set_redirect_uri(RedirectUrl::new("http://example.com/callback".to_string())?); - let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); + let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); - let (auth_url, csrf_token) = oauth_client - .authorize_url(CsrfToken::new_random) - .add_scope(Scope::new("openid".to_string())) - .add_scope(Scope::new("profile".to_string())) - .add_scope(Scope::new("email".to_string())) - .set_pkce_challenge(pkce_challenge) - .url(); + let (auth_url, csrf_token) = oauth_client + .authorize_url(CsrfToken::new_random) + .add_scope(Scope::new("openid".to_string())) + .add_scope(Scope::new("profile".to_string())) + .add_scope(Scope::new("email".to_string())) + .set_pkce_challenge(pkce_challenge) + .url(); - // use curl to handle HTTP basic authentication + // use curl to handle HTTP basic authentication - let args = vec![ - "-w".to_string(), - "%{redirect_url}\n".to_string(), - "-u".to_string(), - "rmalina1:test-password".to_string(), - auth_url.to_string(), - ]; + let args = vec![ + "-w".to_string(), + "%{redirect_url}\n".to_string(), + "-u".to_string(), + "rmalina1:test-password".to_string(), + auth_url.to_string(), + ]; - let curl_output = Command::new("curl").args(args).output()?; + let curl_output = Command::new("curl").args(args).output()?; - // parse URL from redirect to callback with authorization code + // parse URL from redirect to callback with authorization code - let url = Url::parse(std::str::from_utf8(&curl_output.stdout)?.trim())?; + let url = Url::parse(std::str::from_utf8(&curl_output.stdout)?.trim())?; - let mut query_state = None; - let mut query_code = None; + let mut query_state = None; + let mut query_code = None; - for (key, value) in url.query_pairs() { - match key.to_string().as_str() { - "state" => query_state = Some(value), - "code" => query_code = Some(value), - _ => {} - } - } + for (key, value) in url.query_pairs() { + match key.to_string().as_str() { + "state" => query_state = Some(value), + "code" => query_code = Some(value), + _ => {}, + } + } - assert_eq!(*csrf_token.secret(), query_state.unwrap().to_string()); + assert_eq!(*csrf_token.secret(), query_state.unwrap().to_string()); - // exchange authorization code for access token + // exchange authorization code for access token - let auth_code = query_code.unwrap(); - let token_response = oauth_client - .exchange_code(AuthorizationCode::new(auth_code.to_string())) - .set_pkce_verifier(pkce_verifier) - .request(http_client)?; + let auth_code = query_code.unwrap(); + let token_response = oauth_client + .exchange_code(AuthorizationCode::new(auth_code.to_string())) + .set_pkce_verifier(pkce_verifier) + .request(http_client)?; - println!("{}", token_response.access_token().secret()); - Ok(()) + println!("{}", token_response.access_token().secret()); + Ok(()) } diff --git a/crates/opa-tp-protocol/build.rs b/crates/opa-tp-protocol/build.rs index a042026ab..1a99e734b 100644 --- a/crates/opa-tp-protocol/build.rs +++ b/crates/opa-tp-protocol/build.rs @@ -1,20 +1,20 @@ use std::{env, fs, io::Result, path::PathBuf}; fn main() -> Result<()> { - let protos = glob::glob("./src/protos/*.proto") - .unwrap() - .map(|x| x.unwrap()) - .collect::>(); - prost_build::compile_protos(&protos, &["./src/protos"])?; + let protos = glob::glob("./src/protos/*.proto") + .unwrap() + .map(|x| x.unwrap()) + .collect::>(); + prost_build::compile_protos(&protos, &["./src/protos"])?; - let out_str = env::var("OUT_DIR").unwrap(); - let out_path = PathBuf::from(&out_str); - let mut out_path = out_path.ancestors().nth(3).unwrap().to_owned(); - out_path.push("assets"); + let out_str = env::var("OUT_DIR").unwrap(); + let out_path = PathBuf::from(&out_str); + let mut out_path = out_path.ancestors().nth(3).unwrap().to_owned(); + out_path.push("assets"); - if !out_path.exists() { - fs::create_dir(&out_path).expect("Could not create assets dir"); - } + if !out_path.exists() { + fs::create_dir(&out_path).expect("Could not create assets dir"); + } - Ok(()) + Ok(()) } diff --git a/crates/opa-tp-protocol/src/address.rs b/crates/opa-tp-protocol/src/address.rs index 07dfc5ae2..0cd9134b3 100644 --- a/crates/opa-tp-protocol/src/address.rs +++ b/crates/opa-tp-protocol/src/address.rs @@ -3,22 +3,22 @@ use lazy_static::lazy_static; use openssl::sha::Sha256; lazy_static! { - pub static ref PREFIX: String = { - let mut sha = Sha256::new(); - sha.update("opa-tp".as_bytes()); - hex::encode(sha.finish())[..6].to_string() - }; + pub static ref PREFIX: String = { + let mut sha = Sha256::new(); + sha.update("opa-tp".as_bytes()); + hex::encode(sha.finish())[..6].to_string() + }; } pub static VERSION: &str = "1.0"; pub static FAMILY: &str = "opa-tp"; pub fn hash_and_append(addr: impl AsRef) -> String { - let mut sha = Sha256::new(); - sha.update(addr.as_ref().as_bytes()); - format!("{}{}", &*PREFIX, hex::encode(sha.finish())) + let mut sha = Sha256::new(); + sha.update(addr.as_ref().as_bytes()); + format!("{}{}", &*PREFIX, hex::encode(sha.finish())) } pub trait HasSawtoothAddress { - fn get_address(&self) -> String; + fn get_address(&self) -> String; } diff --git a/crates/opa-tp-protocol/src/events.rs b/crates/opa-tp-protocol/src/events.rs index 81893fb33..ec7787772 100644 --- a/crates/opa-tp-protocol/src/events.rs +++ b/crates/opa-tp-protocol/src/events.rs @@ -1,52 +1,50 @@ use async_stl_client::{ - error::SawtoothCommunicationError, - ledger::{LedgerEvent, Span}, + error::SawtoothCommunicationError, + ledger::{LedgerEvent, Span}, }; use prost::Message; use serde_json::json; use crate::{ - messages::{self, OpaEvent}, - state::OpaOperationEvent, + messages::{self, OpaEvent}, + state::OpaOperationEvent, }; #[async_trait::async_trait] impl LedgerEvent for OpaOperationEvent { - async fn deserialize(buf: &[u8]) -> Result<(Self, Span), SawtoothCommunicationError> - where - Self: Sized, - { - let ev = OpaEvent::decode(buf)?; - if let Some(payload) = ev.payload { - match payload { - messages::opa_event::Payload::Operation(value) => { - let value = serde_json::from_str(&value)?; - Ok((value, Span::Span(ev.span_id))) - } - messages::opa_event::Payload::Error(value) => { - Ok((OpaOperationEvent::Error(value), Span::Span(ev.span_id))) - } - } - } else { - Err(SawtoothCommunicationError::MalformedMessage) - } - } + async fn deserialize(buf: &[u8]) -> Result<(Self, Span), SawtoothCommunicationError> + where + Self: Sized, + { + let ev = OpaEvent::decode(buf)?; + if let Some(payload) = ev.payload { + match payload { + messages::opa_event::Payload::Operation(value) => { + let value = serde_json::from_str(&value)?; + Ok((value, Span::Span(ev.span_id))) + }, + messages::opa_event::Payload::Error(value) => + Ok((OpaOperationEvent::Error(value), Span::Span(ev.span_id))), + } + } else { + Err(SawtoothCommunicationError::MalformedMessage) + } + } } pub fn opa_event(span_id: u64, value: OpaOperationEvent) -> Result, serde_json::Error> { - Ok(OpaEvent { - version: crate::PROTOCOL_VERSION.to_string(), - span_id, - payload: { - match value { - OpaOperationEvent::Error(e) => Some(messages::opa_event::Payload::Error( - serde_json::to_string(&json!({ "error": e }))?, - )), - value => Some(messages::opa_event::Payload::Operation( - serde_json::to_string(&value)?, - )), - } - }, - } - .encode_to_vec()) + Ok(OpaEvent { + version: crate::PROTOCOL_VERSION.to_string(), + span_id, + payload: { + match value { + OpaOperationEvent::Error(e) => Some(messages::opa_event::Payload::Error( + serde_json::to_string(&json!({ "error": e }))?, + )), + value => + Some(messages::opa_event::Payload::Operation(serde_json::to_string(&value)?)), + } + }, + } + .encode_to_vec()) } diff --git a/crates/opa-tp-protocol/src/lib.rs b/crates/opa-tp-protocol/src/lib.rs index dce0d62f9..c695ee43a 100644 --- a/crates/opa-tp-protocol/src/lib.rs +++ b/crates/opa-tp-protocol/src/lib.rs @@ -1,8 +1,8 @@ #![cfg_attr(feature = "strict", deny(warnings))] use async_stl_client::{ - ledger::SawtoothLedger, - zmq_client::{RetryingRequestResponseChannel, ZmqRequestResponseSawtoothChannel}, + ledger::SawtoothLedger, + zmq_client::{RetryingRequestResponseChannel, ZmqRequestResponseSawtoothChannel}, }; use state::OpaOperationEvent; use transaction::OpaSubmitTransaction; @@ -17,14 +17,14 @@ pub use async_stl_client; static PROTOCOL_VERSION: &str = "1"; pub type OpaLedger = SawtoothLedger< - RetryingRequestResponseChannel, - OpaOperationEvent, - OpaSubmitTransaction, + RetryingRequestResponseChannel, + OpaOperationEvent, + OpaSubmitTransaction, >; // generated from ./protos/ pub mod messages { - #![allow(clippy::derive_partial_eq_without_eq)] + #![allow(clippy::derive_partial_eq_without_eq)] - include!(concat!(env!("OUT_DIR"), "/_.rs")); + include!(concat!(env!("OUT_DIR"), "/_.rs")); } diff --git a/crates/opa-tp-protocol/src/state.rs b/crates/opa-tp-protocol/src/state.rs index 51cb2bfaa..c1edb4bc7 100644 --- a/crates/opa-tp-protocol/src/state.rs +++ b/crates/opa-tp-protocol/src/state.rs @@ -4,71 +4,71 @@ use crate::address::{hash_and_append, HasSawtoothAddress}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct KeyRegistration { - // Der encoded public key - pub key: String, - pub version: u64, + // Der encoded public key + pub key: String, + pub version: u64, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Keys { - pub id: String, - pub current: KeyRegistration, - pub expired: Option, + pub id: String, + pub current: KeyRegistration, + pub expired: Option, } impl HasSawtoothAddress for Keys { - fn get_address(&self) -> String { - key_address(&self.id) - } + fn get_address(&self) -> String { + key_address(&self.id) + } } pub fn policy_address(id: impl AsRef) -> String { - hash_and_append(format!("opa:policy:binary:{}", id.as_ref())) + hash_and_append(format!("opa:policy:binary:{}", id.as_ref())) } pub fn policy_meta_address(id: impl AsRef) -> String { - hash_and_append(format!("opa:policy:meta:{}", id.as_ref())) + hash_and_append(format!("opa:policy:meta:{}", id.as_ref())) } pub fn key_address(id: impl AsRef) -> String { - hash_and_append(format!("opa:keys:{}", id.as_ref())) + hash_and_append(format!("opa:keys:{}", id.as_ref())) } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PolicyMeta { - pub id: String, - pub hash: String, - pub policy_address: String, + pub id: String, + pub hash: String, + pub policy_address: String, } impl HasSawtoothAddress for PolicyMeta { - fn get_address(&self) -> String { - policy_address(&self.id) - } + fn get_address(&self) -> String { + policy_address(&self.id) + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum OpaOperationEvent { - PolicyUpdate(PolicyMeta), - KeyUpdate(Keys), + PolicyUpdate(PolicyMeta), + KeyUpdate(Keys), - Error(String), + Error(String), } impl From for OpaOperationEvent { - fn from(v: String) -> Self { - Self::Error(v) - } + fn from(v: String) -> Self { + Self::Error(v) + } } impl From for OpaOperationEvent { - fn from(v: Keys) -> Self { - Self::KeyUpdate(v) - } + fn from(v: Keys) -> Self { + Self::KeyUpdate(v) + } } impl From for OpaOperationEvent { - fn from(v: PolicyMeta) -> Self { - Self::PolicyUpdate(v) - } + fn from(v: PolicyMeta) -> Self { + Self::PolicyUpdate(v) + } } diff --git a/crates/opa-tp-protocol/src/submission.rs b/crates/opa-tp-protocol/src/submission.rs index baeb07a51..6724eaa6d 100644 --- a/crates/opa-tp-protocol/src/submission.rs +++ b/crates/opa-tp-protocol/src/submission.rs @@ -1,213 +1,196 @@ use std::{ - cell::RefCell, - sync::{Arc, Mutex}, + cell::RefCell, + sync::{Arc, Mutex}, }; use crate::{messages, PROTOCOL_VERSION}; use chronicle_signing::{ - ChronicleSigning, OpaKnownKeyNamesSigner, SecretError, WithSecret, OPA_NAMESPACE, + ChronicleSigning, OpaKnownKeyNamesSigner, SecretError, WithSecret, OPA_NAMESPACE, }; use k256::{ - ecdsa::{Signature, SigningKey, VerifyingKey}, - pkcs8::{EncodePublicKey, LineEnding}, - schnorr::signature::Signer, - PublicKey, + ecdsa::{Signature, SigningKey, VerifyingKey}, + pkcs8::{EncodePublicKey, LineEnding}, + schnorr::signature::Signer, + PublicKey, }; use prost::Message; fn bootstrap_root(public_key: VerifyingKey) -> messages::BootstrapRoot { - let public_key: PublicKey = public_key.into(); - messages::BootstrapRoot { - public_key: public_key.to_public_key_pem(LineEnding::CRLF).unwrap(), - } + let public_key: PublicKey = public_key.into(); + messages::BootstrapRoot { public_key: public_key.to_public_key_pem(LineEnding::CRLF).unwrap() } } fn register_key( - id: impl AsRef, - public_key: &VerifyingKey, - overwrite_existing: bool, + id: impl AsRef, + public_key: &VerifyingKey, + overwrite_existing: bool, ) -> messages::RegisterKey { - let public_key: PublicKey = public_key.into(); - messages::RegisterKey { - id: id.as_ref().to_string(), - public_key: public_key.to_public_key_pem(LineEnding::CRLF).unwrap(), - overwrite_existing, - } + let public_key: PublicKey = public_key.into(); + messages::RegisterKey { + id: id.as_ref().to_string(), + public_key: public_key.to_public_key_pem(LineEnding::CRLF).unwrap(), + overwrite_existing, + } } fn rotate_key( - id: impl AsRef, - old_key: &SigningKey, - new_key: &SigningKey, + id: impl AsRef, + old_key: &SigningKey, + new_key: &SigningKey, ) -> messages::RotateKey { - let new_key_message = messages::rotate_key::NewPublicKey { - id: id.as_ref().to_string(), - public_key: new_key.to_bytes().to_vec(), - }; - - let new_key_bytes = new_key_message.encode_to_vec(); - - let old_signature: Signature = old_key.sign(&new_key_bytes); - let old_verifying_key = old_key.verifying_key(); - let old_verifying_public_key: PublicKey = old_verifying_key.into(); - - let new_signature: Signature = new_key.sign(&new_key_bytes); - let new_verifying_key = new_key.verifying_key(); - let new_verifying_public_key: PublicKey = new_verifying_key.into(); - - messages::RotateKey { - payload: Some(new_key_message), - previous_signature: old_signature.to_vec(), - previous_signing_key: old_verifying_public_key - .to_public_key_pem(LineEnding::CRLF) - .unwrap(), - new_signature: new_signature.to_vec(), - new_signing_key: new_verifying_public_key - .to_public_key_pem(LineEnding::CRLF) - .unwrap(), - } + let new_key_message = messages::rotate_key::NewPublicKey { + id: id.as_ref().to_string(), + public_key: new_key.to_bytes().to_vec(), + }; + + let new_key_bytes = new_key_message.encode_to_vec(); + + let old_signature: Signature = old_key.sign(&new_key_bytes); + let old_verifying_key = old_key.verifying_key(); + let old_verifying_public_key: PublicKey = old_verifying_key.into(); + + let new_signature: Signature = new_key.sign(&new_key_bytes); + let new_verifying_key = new_key.verifying_key(); + let new_verifying_public_key: PublicKey = new_verifying_key.into(); + + messages::RotateKey { + payload: Some(new_key_message), + previous_signature: old_signature.to_vec(), + previous_signing_key: old_verifying_public_key.to_public_key_pem(LineEnding::CRLF).unwrap(), + new_signature: new_signature.to_vec(), + new_signing_key: new_verifying_public_key.to_public_key_pem(LineEnding::CRLF).unwrap(), + } } fn set_policy(id: impl AsRef, policy: Vec) -> messages::SetPolicy { - messages::SetPolicy { - id: id.as_ref().to_owned(), - policy, - } + messages::SetPolicy { id: id.as_ref().to_owned(), policy } } enum BuildingMessage { - BootstrapRoot(messages::BootstrapRoot), - RegisterKey(messages::SignedOperation), - RotateKey(messages::SignedOperation), - SetPolicy(messages::SignedOperation), + BootstrapRoot(messages::BootstrapRoot), + RegisterKey(messages::SignedOperation), + RotateKey(messages::SignedOperation), + SetPolicy(messages::SignedOperation), } pub struct SubmissionBuilder { - message: Option, + message: Option, } impl SubmissionBuilder { - pub fn bootstrap_root(public_key: VerifyingKey) -> Self { - Self { - message: Some(BuildingMessage::BootstrapRoot(bootstrap_root(public_key))), - } - } - - pub async fn register_key( - id: impl AsRef, - new_key: &str, - signer: &ChronicleSigning, - overwrite_existing: bool, - ) -> Result { - let operation = messages::signed_operation::Payload { - operation: Some(messages::signed_operation::payload::Operation::RegisterKey( - register_key( - id, - &signer.verifying_key(OPA_NAMESPACE, new_key).await?, - overwrite_existing, - ), - )), - }; - - let signature = signer.opa_sign(&operation.encode_to_vec()).await?; - let key: PublicKey = signer.opa_verifying().await?.into(); - let signed_operation = messages::SignedOperation { - payload: Some(operation), - signature: signature.to_vec(), - verifying_key: key.to_public_key_pem(LineEnding::CRLF).unwrap(), - }; - Ok(Self { - message: Some(BuildingMessage::RegisterKey(signed_operation)), - }) - } - - pub async fn rotate_key( - id: &str, - signer: &ChronicleSigning, - old_key: &str, - new_key: &str, - ) -> Result { - let extract_key: Arc>>> = - Arc::new(Mutex::new(None.into())); - - signer - .with_signing_key(OPA_NAMESPACE, old_key, |old_key| { - extract_key.lock().unwrap().replace(Some(old_key.clone())); - }) - .await?; - - let old_key = extract_key.lock().unwrap().borrow().clone().unwrap(); - - signer - .with_signing_key(OPA_NAMESPACE, new_key, |new_key| { - extract_key.lock().unwrap().replace(Some(new_key.clone())); - }) - .await?; - - let new_key = extract_key.lock().unwrap().borrow().clone().unwrap(); - - let operation = messages::signed_operation::Payload { - operation: Some(messages::signed_operation::payload::Operation::RotateKey( - rotate_key(id, &old_key, &new_key), - )), - }; - - let signature = signer.opa_sign(&operation.encode_to_vec()).await?; - let key: PublicKey = signer.opa_verifying().await?.into(); - - let signed_operation = messages::SignedOperation { - payload: Some(operation), - signature, - verifying_key: key.to_public_key_pem(LineEnding::CRLF).unwrap(), - }; - Ok(Self { - message: Some(BuildingMessage::RotateKey(signed_operation)), - }) - } - - pub async fn set_policy( - id: &str, - policy: Vec, - signer: &ChronicleSigning, - ) -> Result { - let operation = messages::signed_operation::Payload { - operation: Some(messages::signed_operation::payload::Operation::SetPolicy( - set_policy(id, policy), - )), - }; - let signature = signer.opa_sign(&operation.encode_to_vec()).await?; - let key: PublicKey = signer.opa_verifying().await?.into(); - - let signed_operation = messages::SignedOperation { - payload: Some(operation), - signature, - verifying_key: key.to_public_key_pem(LineEnding::CRLF).unwrap(), - }; - - Ok(Self { - message: Some(BuildingMessage::SetPolicy(signed_operation)), - }) - } - - pub fn build(mut self, span_id: u64) -> messages::Submission { - let mut submission = messages::Submission::default(); - match self.message.take().unwrap() { - BuildingMessage::BootstrapRoot(message) => { - submission.payload = Some(messages::submission::Payload::BootstrapRoot(message)); - } - BuildingMessage::RotateKey(message) => { - submission.payload = Some(messages::submission::Payload::SignedOperation(message)); - } - BuildingMessage::SetPolicy(message) => { - submission.payload = Some(messages::submission::Payload::SignedOperation(message)); - } - BuildingMessage::RegisterKey(message) => { - submission.payload = Some(messages::submission::Payload::SignedOperation(message)); - } - }; - submission.span_id = span_id; - submission.version = PROTOCOL_VERSION.to_string(); - - submission - } + pub fn bootstrap_root(public_key: VerifyingKey) -> Self { + Self { message: Some(BuildingMessage::BootstrapRoot(bootstrap_root(public_key))) } + } + + pub async fn register_key( + id: impl AsRef, + new_key: &str, + signer: &ChronicleSigning, + overwrite_existing: bool, + ) -> Result { + let operation = messages::signed_operation::Payload { + operation: Some(messages::signed_operation::payload::Operation::RegisterKey( + register_key( + id, + &signer.verifying_key(OPA_NAMESPACE, new_key).await?, + overwrite_existing, + ), + )), + }; + + let signature = signer.opa_sign(&operation.encode_to_vec()).await?; + let key: PublicKey = signer.opa_verifying().await?.into(); + let signed_operation = messages::SignedOperation { + payload: Some(operation), + signature: signature.to_vec(), + verifying_key: key.to_public_key_pem(LineEnding::CRLF).unwrap(), + }; + Ok(Self { message: Some(BuildingMessage::RegisterKey(signed_operation)) }) + } + + pub async fn rotate_key( + id: &str, + signer: &ChronicleSigning, + old_key: &str, + new_key: &str, + ) -> Result { + let extract_key: Arc>>> = + Arc::new(Mutex::new(None.into())); + + signer + .with_signing_key(OPA_NAMESPACE, old_key, |old_key| { + extract_key.lock().unwrap().replace(Some(old_key.clone())); + }) + .await?; + + let old_key = extract_key.lock().unwrap().borrow().clone().unwrap(); + + signer + .with_signing_key(OPA_NAMESPACE, new_key, |new_key| { + extract_key.lock().unwrap().replace(Some(new_key.clone())); + }) + .await?; + + let new_key = extract_key.lock().unwrap().borrow().clone().unwrap(); + + let operation = messages::signed_operation::Payload { + operation: Some(messages::signed_operation::payload::Operation::RotateKey(rotate_key( + id, &old_key, &new_key, + ))), + }; + + let signature = signer.opa_sign(&operation.encode_to_vec()).await?; + let key: PublicKey = signer.opa_verifying().await?.into(); + + let signed_operation = messages::SignedOperation { + payload: Some(operation), + signature, + verifying_key: key.to_public_key_pem(LineEnding::CRLF).unwrap(), + }; + Ok(Self { message: Some(BuildingMessage::RotateKey(signed_operation)) }) + } + + pub async fn set_policy( + id: &str, + policy: Vec, + signer: &ChronicleSigning, + ) -> Result { + let operation = messages::signed_operation::Payload { + operation: Some(messages::signed_operation::payload::Operation::SetPolicy(set_policy( + id, policy, + ))), + }; + let signature = signer.opa_sign(&operation.encode_to_vec()).await?; + let key: PublicKey = signer.opa_verifying().await?.into(); + + let signed_operation = messages::SignedOperation { + payload: Some(operation), + signature, + verifying_key: key.to_public_key_pem(LineEnding::CRLF).unwrap(), + }; + + Ok(Self { message: Some(BuildingMessage::SetPolicy(signed_operation)) }) + } + + pub fn build(mut self, span_id: u64) -> messages::Submission { + let mut submission = messages::Submission::default(); + match self.message.take().unwrap() { + BuildingMessage::BootstrapRoot(message) => { + submission.payload = Some(messages::submission::Payload::BootstrapRoot(message)); + }, + BuildingMessage::RotateKey(message) => { + submission.payload = Some(messages::submission::Payload::SignedOperation(message)); + }, + BuildingMessage::SetPolicy(message) => { + submission.payload = Some(messages::submission::Payload::SignedOperation(message)); + }, + BuildingMessage::RegisterKey(message) => { + submission.payload = Some(messages::submission::Payload::SignedOperation(message)); + }, + }; + submission.span_id = span_id; + submission.version = PROTOCOL_VERSION.to_string(); + + submission + } } diff --git a/crates/opa-tp-protocol/src/transaction.rs b/crates/opa-tp-protocol/src/transaction.rs index af494970a..19ffb6ce7 100644 --- a/crates/opa-tp-protocol/src/transaction.rs +++ b/crates/opa-tp-protocol/src/transaction.rs @@ -1,172 +1,163 @@ use std::{convert::Infallible, sync::Arc}; use async_stl_client::{ - ledger::{LedgerTransaction, TransactionId}, - sawtooth::MessageBuilder, + ledger::{LedgerTransaction, TransactionId}, + sawtooth::MessageBuilder, }; use chronicle_signing::{BatcherKnownKeyNamesSigner, ChronicleSigning, SecretError}; use k256::ecdsa::VerifyingKey; use prost::Message; use crate::{ - async_stl_client::sawtooth::TransactionPayload, - messages::Submission, - state::{key_address, policy_address, policy_meta_address}, + async_stl_client::sawtooth::TransactionPayload, + messages::Submission, + state::{key_address, policy_address, policy_meta_address}, }; #[derive(Debug, Clone)] pub enum OpaSubmitTransaction { - BootstrapRoot(Submission, ChronicleSigning), - RotateRoot(Submission, ChronicleSigning), - RegisterKey(Submission, ChronicleSigning, String, bool), - RotateKey(Submission, ChronicleSigning, String), - SetPolicy(Submission, ChronicleSigning, String), + BootstrapRoot(Submission, ChronicleSigning), + RotateRoot(Submission, ChronicleSigning), + RegisterKey(Submission, ChronicleSigning, String, bool), + RotateKey(Submission, ChronicleSigning, String), + SetPolicy(Submission, ChronicleSigning, String), } impl OpaSubmitTransaction { - pub fn bootstrap_root(submission: Submission, sawtooth_signer: &ChronicleSigning) -> Self { - Self::BootstrapRoot(submission, sawtooth_signer.to_owned()) - } - - pub fn rotate_root(submission: Submission, sawtooth_signer: &ChronicleSigning) -> Self { - Self::RotateRoot(submission, sawtooth_signer.to_owned()) - } - - pub fn register_key( - name: impl AsRef, - submission: Submission, - sawtooth_signer: &ChronicleSigning, - overwrite_existing: bool, - ) -> Self { - Self::RegisterKey( - submission, - sawtooth_signer.to_owned(), - name.as_ref().to_owned(), - overwrite_existing, - ) - } - - pub fn rotate_key( - name: impl AsRef, - submission: Submission, - sawtooth_signer: &ChronicleSigning, - ) -> Self { - Self::RegisterKey( - submission, - sawtooth_signer.to_owned(), - name.as_ref().to_owned(), - false, - ) - } - - pub fn set_policy( - name: impl AsRef, - submission: Submission, - sawtooth_signer: &ChronicleSigning, - ) -> Self { - Self::SetPolicy( - submission, - sawtooth_signer.to_owned(), - name.as_ref().to_owned(), - ) - } + pub fn bootstrap_root(submission: Submission, sawtooth_signer: &ChronicleSigning) -> Self { + Self::BootstrapRoot(submission, sawtooth_signer.to_owned()) + } + + pub fn rotate_root(submission: Submission, sawtooth_signer: &ChronicleSigning) -> Self { + Self::RotateRoot(submission, sawtooth_signer.to_owned()) + } + + pub fn register_key( + name: impl AsRef, + submission: Submission, + sawtooth_signer: &ChronicleSigning, + overwrite_existing: bool, + ) -> Self { + Self::RegisterKey( + submission, + sawtooth_signer.to_owned(), + name.as_ref().to_owned(), + overwrite_existing, + ) + } + + pub fn rotate_key( + name: impl AsRef, + submission: Submission, + sawtooth_signer: &ChronicleSigning, + ) -> Self { + Self::RegisterKey(submission, sawtooth_signer.to_owned(), name.as_ref().to_owned(), false) + } + + pub fn set_policy( + name: impl AsRef, + submission: Submission, + sawtooth_signer: &ChronicleSigning, + ) -> Self { + Self::SetPolicy(submission, sawtooth_signer.to_owned(), name.as_ref().to_owned()) + } } #[async_trait::async_trait] impl TransactionPayload for OpaSubmitTransaction { - type Error = Infallible; - - /// Envelope a payload of `ChronicleOperations` and `SignedIdentity` in a `Submission` protocol buffer, - /// along with placeholders for protocol version info and a tracing span id. - async fn to_bytes(&self) -> Result, Infallible> { - Ok(match self { - Self::BootstrapRoot(submission, _) => submission, - Self::RotateRoot(submission, _) => submission, - Self::RegisterKey(submission, _, _, _) => submission, - Self::RotateKey(submission, _, _) => submission, - Self::SetPolicy(submission, _, _) => submission, - } - .encode_to_vec()) - } + type Error = Infallible; + + /// Envelope a payload of `ChronicleOperations` and `SignedIdentity` in a `Submission` protocol + /// buffer, along with placeholders for protocol version info and a tracing span id. + async fn to_bytes(&self) -> Result, Infallible> { + Ok(match self { + Self::BootstrapRoot(submission, _) => submission, + Self::RotateRoot(submission, _) => submission, + Self::RegisterKey(submission, _, _, _) => submission, + Self::RotateKey(submission, _, _) => submission, + Self::SetPolicy(submission, _, _) => submission, + } + .encode_to_vec()) + } } #[async_trait::async_trait] impl LedgerTransaction for OpaSubmitTransaction { - type Error = SecretError; - - async fn sign(&self, bytes: Arc>) -> Result, Self::Error> { - let signer = match self { - Self::BootstrapRoot(_, signer) => signer, - Self::RotateRoot(_, signer) => signer, - Self::RegisterKey(_, signer, _, _) => signer, - Self::RotateKey(_, signer, _) => signer, - Self::SetPolicy(_, signer, _) => signer, - }; - signer.batcher_sign(&bytes).await - } - - async fn verifying_key(&self) -> Result { - let signer = match self { - Self::BootstrapRoot(_, signer) => signer, - Self::RotateRoot(_, signer) => signer, - Self::RegisterKey(_, signer, _, _) => signer, - Self::RotateKey(_, signer, _) => signer, - Self::SetPolicy(_, signer, _) => signer, - }; - - signer.batcher_verifying().await - } - - fn addresses(&self) -> Vec { - match self { - Self::BootstrapRoot(_, _) => { - vec![key_address("root")] - } - Self::RotateRoot(_, _) => { - vec![key_address("root")] - } - Self::RegisterKey(_, _, name, _) => { - vec![key_address("root"), key_address(name.clone())] - } - Self::RotateKey(_, _, name) => { - vec![key_address("root"), key_address(name.clone())] - } - Self::SetPolicy(_, _, name) => { - vec![ - key_address("root"), - policy_meta_address(name.clone()), - policy_address(name.clone()), - ] - } - } - } - - async fn as_sawtooth_tx( - &self, - message_builder: &MessageBuilder, - ) -> Result<(async_stl_client::messages::Transaction, TransactionId), Self::Error> { - let signer = match self { - Self::BootstrapRoot(_, signer) => signer, - Self::RotateRoot(_, signer) => signer, - Self::RegisterKey(_, signer, _, _) => signer, - Self::RotateKey(_, signer, _) => signer, - Self::SetPolicy(_, signer, _) => signer, - } - .clone(); - - message_builder - .make_sawtooth_transaction( - self.addresses(), - self.addresses(), - vec![], - self, - signer.batcher_verifying().await?, - |bytes| { - let signer = signer.clone(); - let bytes = bytes.to_vec(); - async move { signer.batcher_sign(&bytes).await } - }, - ) - .await - } + type Error = SecretError; + + async fn sign(&self, bytes: Arc>) -> Result, Self::Error> { + let signer = match self { + Self::BootstrapRoot(_, signer) => signer, + Self::RotateRoot(_, signer) => signer, + Self::RegisterKey(_, signer, _, _) => signer, + Self::RotateKey(_, signer, _) => signer, + Self::SetPolicy(_, signer, _) => signer, + }; + signer.batcher_sign(&bytes).await + } + + async fn verifying_key(&self) -> Result { + let signer = match self { + Self::BootstrapRoot(_, signer) => signer, + Self::RotateRoot(_, signer) => signer, + Self::RegisterKey(_, signer, _, _) => signer, + Self::RotateKey(_, signer, _) => signer, + Self::SetPolicy(_, signer, _) => signer, + }; + + signer.batcher_verifying().await + } + + fn addresses(&self) -> Vec { + match self { + Self::BootstrapRoot(_, _) => { + vec![key_address("root")] + }, + Self::RotateRoot(_, _) => { + vec![key_address("root")] + }, + Self::RegisterKey(_, _, name, _) => { + vec![key_address("root"), key_address(name.clone())] + }, + Self::RotateKey(_, _, name) => { + vec![key_address("root"), key_address(name.clone())] + }, + Self::SetPolicy(_, _, name) => { + vec![ + key_address("root"), + policy_meta_address(name.clone()), + policy_address(name.clone()), + ] + }, + } + } + + async fn as_sawtooth_tx( + &self, + message_builder: &MessageBuilder, + ) -> Result<(async_stl_client::messages::Transaction, TransactionId), Self::Error> { + let signer = match self { + Self::BootstrapRoot(_, signer) => signer, + Self::RotateRoot(_, signer) => signer, + Self::RegisterKey(_, signer, _, _) => signer, + Self::RotateKey(_, signer, _) => signer, + Self::SetPolicy(_, signer, _) => signer, + } + .clone(); + + message_builder + .make_sawtooth_transaction( + self.addresses(), + self.addresses(), + vec![], + self, + signer.batcher_verifying().await?, + |bytes| { + let signer = signer.clone(); + let bytes = bytes.to_vec(); + async move { signer.batcher_sign(&bytes).await } + }, + ) + .await + } } diff --git a/crates/opa-tp/build.rs b/crates/opa-tp/build.rs index 5a1d86bbe..afb2c9546 100644 --- a/crates/opa-tp/build.rs +++ b/crates/opa-tp/build.rs @@ -1,8 +1,8 @@ fn main() { - //Create a .VERSION file containing 'local' if it does not exist + //Create a .VERSION file containing 'local' if it does not exist - let version_file = std::path::Path::new("../../.VERSION"); - if !version_file.exists() { - std::fs::write(version_file, "local").expect("Unable to write file"); - } + let version_file = std::path::Path::new("../../.VERSION"); + if !version_file.exists() { + std::fs::write(version_file, "local").expect("Unable to write file"); + } } diff --git a/crates/opa-tp/src/abstract_tp.rs b/crates/opa-tp/src/abstract_tp.rs index 2bbced70c..8ab3efe38 100644 --- a/crates/opa-tp/src/abstract_tp.rs +++ b/crates/opa-tp/src/abstract_tp.rs @@ -1,11 +1,11 @@ use sawtooth_sdk::{ - messages::processor::TpProcessRequest, - processor::handler::{ApplyError, TransactionContext}, + messages::processor::TpProcessRequest, + processor::handler::{ApplyError, TransactionContext}, }; pub trait TP { - fn apply( - &self, - request: &TpProcessRequest, - context: &mut dyn TransactionContext, - ) -> Result<(), ApplyError>; + fn apply( + &self, + request: &TpProcessRequest, + context: &mut dyn TransactionContext, + ) -> Result<(), ApplyError>; } diff --git a/crates/opa-tp/src/main.rs b/crates/opa-tp/src/main.rs index 8070c5d01..01d0533aa 100644 --- a/crates/opa-tp/src/main.rs +++ b/crates/opa-tp/src/main.rs @@ -11,74 +11,72 @@ use tracing::info; use url::Url; pub const LONG_VERSION: &str = const_format::formatcp!( - "{}:{}", - env!("CARGO_PKG_VERSION"), - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../.VERSION")) + "{}:{}", + env!("CARGO_PKG_VERSION"), + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../.VERSION")) ); #[tokio::main] async fn main() { - let matches = Command::new("opa-tp") - .version(LONG_VERSION) - .author("Blockchain Technology Partners") - .about("PKI and OPA rule storage") - .arg( - Arg::new("connect") - .short('C') - .long("connect") - .value_hint(ValueHint::Url) - .help("Sets sawtooth validator address") - .takes_value(true), - ) - .arg( - Arg::new("completions") - .long("completions") - .value_name("completions") - .value_parser(PossibleValuesParser::new(["bash", "zsh", "fish"])) - .help("Generate shell completions and exit"), - ) - .arg( - Arg::new("instrument") - .long("instrument") - .value_name("instrument") - .takes_value(true) - .value_hint(ValueHint::Url) - .help("Instrument using RUST_LOG environment"), - ) - .arg( - Arg::new("console-logging") - .long("console-logging") - .value_name("console-logging") - .takes_value(true) - .help("Log to console using RUST_LOG environment"), - ) - .get_matches(); + let matches = Command::new("opa-tp") + .version(LONG_VERSION) + .author("Blockchain Technology Partners") + .about("PKI and OPA rule storage") + .arg( + Arg::new("connect") + .short('C') + .long("connect") + .value_hint(ValueHint::Url) + .help("Sets sawtooth validator address") + .takes_value(true), + ) + .arg( + Arg::new("completions") + .long("completions") + .value_name("completions") + .value_parser(PossibleValuesParser::new(["bash", "zsh", "fish"])) + .help("Generate shell completions and exit"), + ) + .arg( + Arg::new("instrument") + .long("instrument") + .value_name("instrument") + .takes_value(true) + .value_hint(ValueHint::Url) + .help("Instrument using RUST_LOG environment"), + ) + .arg( + Arg::new("console-logging") + .long("console-logging") + .value_name("console-logging") + .takes_value(true) + .help("Log to console using RUST_LOG environment"), + ) + .get_matches(); - telemetry( - matches - .get_one::("instrument") - .and_then(|s| Url::parse(s).ok()), - match matches.get_one::("console-logging") { - Some(level) => match level.as_str() { - "pretty" => ConsoleLogging::Pretty, - "json" => ConsoleLogging::Json, - _ => ConsoleLogging::Off, - }, - _ => ConsoleLogging::Off, - }, - ); + telemetry( + matches.get_one::("instrument").and_then(|s| Url::parse(s).ok()), + match matches.get_one::("console-logging") { + Some(level) => match level.as_str() { + "pretty" => ConsoleLogging::Pretty, + "json" => ConsoleLogging::Json, + _ => ConsoleLogging::Off, + }, + _ => ConsoleLogging::Off, + }, + ); - info!(opa_tp_version = LONG_VERSION); + info!(opa_tp_version = LONG_VERSION); - let handler = OpaTransactionHandler::new(); - let mut processor = TransactionProcessor::new({ - if let Some(connect) = matches.get_one::("connect") { - connect - } else { - "tcp://127.0.0.1:4004" - } - }); + let handler = OpaTransactionHandler::new(); + let mut processor = TransactionProcessor::new({ + if let Some(connect) = matches.get_one::("connect") { + connect + } else { + "tcp://127.0.0.1:4004" + } + }); - processor.add_handler(&handler); - processor.start(); + processor.add_handler(&handler); + processor.start(); } diff --git a/crates/opa-tp/src/tp.rs b/crates/opa-tp/src/tp.rs index 9c8031c51..087c30db4 100644 --- a/crates/opa-tp/src/tp.rs +++ b/crates/opa-tp/src/tp.rs @@ -1,24 +1,24 @@ use k256::{ - ecdsa::signature::Verifier, - pkcs8::DecodePublicKey, - sha2::{Digest, Sha256}, + ecdsa::signature::Verifier, + pkcs8::DecodePublicKey, + sha2::{Digest, Sha256}, }; use opa_tp_protocol::{ - address::{HasSawtoothAddress, FAMILY, PREFIX, VERSION}, - events::opa_event, - messages::Submission, - state::{ - key_address, policy_address, policy_meta_address, KeyRegistration, Keys, OpaOperationEvent, - PolicyMeta, - }, + address::{HasSawtoothAddress, FAMILY, PREFIX, VERSION}, + events::opa_event, + messages::Submission, + state::{ + key_address, policy_address, policy_meta_address, KeyRegistration, Keys, OpaOperationEvent, + PolicyMeta, + }, }; use std::str::from_utf8; use k256::ecdsa::{Signature, VerifyingKey}; use prost::Message; use sawtooth_sdk::{ - messages::processor::TpProcessRequest, - processor::handler::{ApplyError, TransactionContext, TransactionHandler}, + messages::processor::TpProcessRequest, + processor::handler::{ApplyError, TransactionContext, TransactionHandler}, }; use tracing::{debug, error, instrument}; @@ -27,41 +27,41 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum OpaTpError { - #[error("Operation signature verification failed")] - OperationSignatureVerification, - #[error("Submission message not well formed")] - MalformedMessage, - #[error("Invalid signing key")] - InvalidSigningKey, - #[error("Json serialization error {0}")] - JsonSerialize(#[from] serde_json::Error), - #[error("Invalid operation")] - InvalidOperation, - #[error("Sawtooth context {0}")] - SawtoothContext(#[from] sawtooth_sdk::processor::handler::ContextError), + #[error("Operation signature verification failed")] + OperationSignatureVerification, + #[error("Submission message not well formed")] + MalformedMessage, + #[error("Invalid signing key")] + InvalidSigningKey, + #[error("Json serialization error {0}")] + JsonSerialize(#[from] serde_json::Error), + #[error("Invalid operation")] + InvalidOperation, + #[error("Sawtooth context {0}")] + SawtoothContext(#[from] sawtooth_sdk::processor::handler::ContextError), } #[derive(Debug)] pub struct OpaTransactionHandler { - family_name: String, - family_versions: Vec, - namespaces: Vec, + family_name: String, + family_versions: Vec, + namespaces: Vec, } impl OpaTransactionHandler { - pub fn new() -> OpaTransactionHandler { - OpaTransactionHandler { - family_name: FAMILY.to_owned(), - family_versions: vec![VERSION.to_owned()], - namespaces: vec![PREFIX.to_string()], - } - } + pub fn new() -> OpaTransactionHandler { + OpaTransactionHandler { + family_name: FAMILY.to_owned(), + family_versions: vec![VERSION.to_owned()], + namespaces: vec![PREFIX.to_string()], + } + } } impl Default for OpaTransactionHandler { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } // Verifies the submission. @@ -71,334 +71,313 @@ impl Default for OpaTransactionHandler { // key of the operation #[instrument(skip(submission, root_keys), ret(Debug))] fn verify_signed_operation( - submission: &Submission, - root_keys: &Option, + submission: &Submission, + root_keys: &Option, ) -> Result<(), OpaTpError> { - match &submission.payload { - Some(opa_tp_protocol::messages::submission::Payload::BootstrapRoot(_)) => Ok(()), - Some(opa_tp_protocol::messages::submission::Payload::SignedOperation( - opa_tp_protocol::messages::SignedOperation { - payload: Some(ref payload), - verifying_key, - signature, - }, - )) => { - if root_keys.is_none() { - error!("No registered root keys for signature verification"); - return Err(OpaTpError::OperationSignatureVerification); - } - let payload_bytes = payload.encode_to_vec(); - let signature: Signature = k256::ecdsa::signature::Signature::from_bytes(signature) - .map_err(|e| { - error!(signature = ?signature, signature_load_error = ?e); - OpaTpError::OperationSignatureVerification - })?; - let signing_key = VerifyingKey::from_public_key_pem(verifying_key).map_err(|e| { - error!(verifying_key = ?verifying_key, key_load_error = ?e); - OpaTpError::OperationSignatureVerification - })?; - signing_key - .verify(&payload_bytes, &signature) - .map_err(|e| { - error!(signature = ?signature, verify_error = ?e); - OpaTpError::OperationSignatureVerification - })?; - - if *verifying_key == root_keys.as_ref().unwrap().current.key { - Ok(()) - } else { - error!(verifying_key = ?verifying_key, current_key = ?root_keys.as_ref().unwrap().current.key, "Invalid signing key"); - Err(OpaTpError::InvalidSigningKey) - } - } - _ => { - error!(malformed_message = ?submission); - Err(OpaTpError::MalformedMessage) - } - } + match &submission.payload { + Some(opa_tp_protocol::messages::submission::Payload::BootstrapRoot(_)) => Ok(()), + Some(opa_tp_protocol::messages::submission::Payload::SignedOperation( + opa_tp_protocol::messages::SignedOperation { + payload: Some(ref payload), + verifying_key, + signature, + }, + )) => { + if root_keys.is_none() { + error!("No registered root keys for signature verification"); + return Err(OpaTpError::OperationSignatureVerification) + } + let payload_bytes = payload.encode_to_vec(); + let signature: Signature = k256::ecdsa::signature::Signature::from_bytes(signature) + .map_err(|e| { + error!(signature = ?signature, signature_load_error = ?e); + OpaTpError::OperationSignatureVerification + })?; + let signing_key = VerifyingKey::from_public_key_pem(verifying_key).map_err(|e| { + error!(verifying_key = ?verifying_key, key_load_error = ?e); + OpaTpError::OperationSignatureVerification + })?; + signing_key.verify(&payload_bytes, &signature).map_err(|e| { + error!(signature = ?signature, verify_error = ?e); + OpaTpError::OperationSignatureVerification + })?; + + if *verifying_key == root_keys.as_ref().unwrap().current.key { + Ok(()) + } else { + error!(verifying_key = ?verifying_key, current_key = ?root_keys.as_ref().unwrap().current.key, "Invalid signing key"); + Err(OpaTpError::InvalidSigningKey) + } + }, + _ => { + error!(malformed_message = ?submission); + Err(OpaTpError::MalformedMessage) + }, + } } // Either apply our bootstrap operation or our signed operation #[instrument(skip(context, request, payload), ret(Debug))] fn apply_signed_operation( - payload: opa_tp_protocol::messages::submission::Payload, - request: &TpProcessRequest, - context: &mut dyn TransactionContext, + payload: opa_tp_protocol::messages::submission::Payload, + request: &TpProcessRequest, + context: &mut dyn TransactionContext, ) -> Result<(), OpaTpError> { - match payload { - opa_tp_protocol::messages::submission::Payload::BootstrapRoot( - opa_tp_protocol::messages::BootstrapRoot { public_key }, - ) => { - let existing_key = context.get_state_entry(&key_address("root"))?; - - if existing_key.is_some() { - error!("OPA TP has already been bootstrapped"); - return Err(OpaTpError::InvalidOperation); - } - - let keys = Keys { - id: "root".to_string(), - current: KeyRegistration { - key: public_key, - version: 0, - }, - expired: None, - }; - - context.set_state_entry( - keys.get_address(), - serde_json::to_string(&keys)?.into_bytes(), - )?; - - context.add_event( - "opa/operation".to_string(), - vec![("transaction_id".to_string(), request.signature.clone())], - &opa_event(1, keys.into())?, - )?; - - Ok(()) - } - opa_tp_protocol::messages::submission::Payload::SignedOperation( - opa_tp_protocol::messages::SignedOperation { - payload: - Some(opa_tp_protocol::messages::signed_operation::Payload { - operation: Some(operation), - }), - verifying_key: _, - signature: _, - }, - ) => apply_signed_operation_payload(request, operation, context), - _ => { - error!(malformed_message = ?payload); - Err(OpaTpError::MalformedMessage) - } - } + match payload { + opa_tp_protocol::messages::submission::Payload::BootstrapRoot( + opa_tp_protocol::messages::BootstrapRoot { public_key }, + ) => { + let existing_key = context.get_state_entry(&key_address("root"))?; + + if existing_key.is_some() { + error!("OPA TP has already been bootstrapped"); + return Err(OpaTpError::InvalidOperation) + } + + let keys = Keys { + id: "root".to_string(), + current: KeyRegistration { key: public_key, version: 0 }, + expired: None, + }; + + context + .set_state_entry(keys.get_address(), serde_json::to_string(&keys)?.into_bytes())?; + + context.add_event( + "opa/operation".to_string(), + vec![("transaction_id".to_string(), request.signature.clone())], + &opa_event(1, keys.into())?, + )?; + + Ok(()) + }, + opa_tp_protocol::messages::submission::Payload::SignedOperation( + opa_tp_protocol::messages::SignedOperation { + payload: + Some(opa_tp_protocol::messages::signed_operation::Payload { + operation: Some(operation), + }), + verifying_key: _, + signature: _, + }, + ) => apply_signed_operation_payload(request, operation, context), + _ => { + error!(malformed_message = ?payload); + Err(OpaTpError::MalformedMessage) + }, + } } #[instrument(skip(request, context, payload), ret(Debug))] fn apply_signed_operation_payload( - request: &TpProcessRequest, - payload: opa_tp_protocol::messages::signed_operation::payload::Operation, - context: &mut dyn TransactionContext, + request: &TpProcessRequest, + payload: opa_tp_protocol::messages::signed_operation::payload::Operation, + context: &mut dyn TransactionContext, ) -> Result<(), OpaTpError> { - match payload { - opa_tp_protocol::messages::signed_operation::payload::Operation::RegisterKey( - opa_tp_protocol::messages::RegisterKey { - public_key, - id, - overwrite_existing, - }, - ) => { - if id == "root" { - error!("Cannot register a key with the id 'root'"); - return Err(OpaTpError::InvalidOperation); - } - - let existing_key = context.get_state_entry(&key_address(id.clone()))?; - if existing_key.is_some() { - if overwrite_existing { - debug!("Registration replaces existing key"); - } else { - error!("Key already registered"); - return Err(OpaTpError::InvalidOperation); - } - } - - let keys = Keys { - id, - current: KeyRegistration { - key: public_key, - version: 0, - }, - expired: None, - }; - - context.set_state_entry( - keys.get_address(), - serde_json::to_string(&keys)?.into_bytes(), - )?; - - context.add_event( - "opa/operation".to_string(), - vec![("transaction_id".to_string(), request.signature.clone())], - &opa_event(1, keys.into())?, - )?; - - Ok(()) - } - opa_tp_protocol::messages::signed_operation::payload::Operation::RotateKey( - opa_tp_protocol::messages::RotateKey { - payload: Some(payload), - previous_signing_key, - previous_signature, - new_signing_key, - new_signature, - }, - ) => { - // Get current key registration from state - let existing_key = context.get_state_entry(&key_address(payload.id.clone()))?; - - if existing_key.is_none() { - error!("No key to rotate"); - return Err(OpaTpError::InvalidOperation); - } - - let existing_key: Keys = serde_json::from_str( - from_utf8(&existing_key.unwrap()).map_err(|_| OpaTpError::MalformedMessage)?, - )?; - - if previous_signing_key != existing_key.current.key { - error!("Key does not match current key"); - return Err(OpaTpError::InvalidOperation); - } - - // Verify the previous key and signature - let payload_bytes = payload.encode_to_vec(); - let previous_signature = Signature::try_from(&*previous_signature) - .map_err(|_| OpaTpError::OperationSignatureVerification)?; - let previous_key = VerifyingKey::from_public_key_pem(&previous_signing_key) - .map_err(|_| OpaTpError::OperationSignatureVerification)?; - - previous_key - .verify(&payload_bytes, &previous_signature) - .map_err(|_| OpaTpError::OperationSignatureVerification)?; - - //Verify the new key and signature - let new_signature = Signature::try_from(&*new_signature) - .map_err(|_| OpaTpError::OperationSignatureVerification)?; - let new_key = VerifyingKey::from_public_key_pem(&new_signing_key) - .map_err(|_| OpaTpError::OperationSignatureVerification)?; - - new_key - .verify(&payload_bytes, &new_signature) - .map_err(|_| OpaTpError::OperationSignatureVerification)?; - - //Store new keys - let keys = Keys { - id: payload.id, - current: KeyRegistration { - key: new_signing_key, - version: existing_key.current.version + 1, - }, - expired: Some(KeyRegistration { - key: previous_signing_key, - version: existing_key.current.version, - }), - }; - - context.set_state_entry( - keys.get_address(), - serde_json::to_string(&keys)?.into_bytes(), - )?; - - context.add_event( - "opa/operation".to_string(), - vec![("transaction_id".to_string(), request.signature.clone())], - &opa_event(1, keys.into())?, - )?; - - Ok(()) - } - opa_tp_protocol::messages::signed_operation::payload::Operation::SetPolicy( - opa_tp_protocol::messages::SetPolicy { policy, id }, - ) => { - let _existing_policy_meta = context.get_state_entry(&policy_meta_address(&*id))?; - let hash = Sha256::digest(&policy); - let hash = hex::encode(hash); - - let policy_meta = PolicyMeta { - id: id.clone(), - hash, - policy_address: policy_address(&*id), - }; - - context.set_state_entry( - policy_meta_address(&id), - serde_json::to_string(&policy_meta)?.into_bytes(), - )?; - - context.set_state_entry(policy_address(&id), policy)?; - - context.add_event( - "opa/operation".to_string(), - vec![("transaction_id".to_string(), request.signature.clone())], - &opa_event(1, policy_meta.into())?, - )?; - - Ok(()) - } - _ => Err(OpaTpError::MalformedMessage), - } + match payload { + opa_tp_protocol::messages::signed_operation::payload::Operation::RegisterKey( + opa_tp_protocol::messages::RegisterKey { public_key, id, overwrite_existing }, + ) => { + if id == "root" { + error!("Cannot register a key with the id 'root'"); + return Err(OpaTpError::InvalidOperation) + } + + let existing_key = context.get_state_entry(&key_address(id.clone()))?; + if existing_key.is_some() { + if overwrite_existing { + debug!("Registration replaces existing key"); + } else { + error!("Key already registered"); + return Err(OpaTpError::InvalidOperation) + } + } + + let keys = Keys { + id, + current: KeyRegistration { key: public_key, version: 0 }, + expired: None, + }; + + context + .set_state_entry(keys.get_address(), serde_json::to_string(&keys)?.into_bytes())?; + + context.add_event( + "opa/operation".to_string(), + vec![("transaction_id".to_string(), request.signature.clone())], + &opa_event(1, keys.into())?, + )?; + + Ok(()) + }, + opa_tp_protocol::messages::signed_operation::payload::Operation::RotateKey( + opa_tp_protocol::messages::RotateKey { + payload: Some(payload), + previous_signing_key, + previous_signature, + new_signing_key, + new_signature, + }, + ) => { + // Get current key registration from state + let existing_key = context.get_state_entry(&key_address(payload.id.clone()))?; + + if existing_key.is_none() { + error!("No key to rotate"); + return Err(OpaTpError::InvalidOperation) + } + + let existing_key: Keys = serde_json::from_str( + from_utf8(&existing_key.unwrap()).map_err(|_| OpaTpError::MalformedMessage)?, + )?; + + if previous_signing_key != existing_key.current.key { + error!("Key does not match current key"); + return Err(OpaTpError::InvalidOperation) + } + + // Verify the previous key and signature + let payload_bytes = payload.encode_to_vec(); + let previous_signature = Signature::try_from(&*previous_signature) + .map_err(|_| OpaTpError::OperationSignatureVerification)?; + let previous_key = VerifyingKey::from_public_key_pem(&previous_signing_key) + .map_err(|_| OpaTpError::OperationSignatureVerification)?; + + previous_key + .verify(&payload_bytes, &previous_signature) + .map_err(|_| OpaTpError::OperationSignatureVerification)?; + + //Verify the new key and signature + let new_signature = Signature::try_from(&*new_signature) + .map_err(|_| OpaTpError::OperationSignatureVerification)?; + let new_key = VerifyingKey::from_public_key_pem(&new_signing_key) + .map_err(|_| OpaTpError::OperationSignatureVerification)?; + + new_key + .verify(&payload_bytes, &new_signature) + .map_err(|_| OpaTpError::OperationSignatureVerification)?; + + //Store new keys + let keys = Keys { + id: payload.id, + current: KeyRegistration { + key: new_signing_key, + version: existing_key.current.version + 1, + }, + expired: Some(KeyRegistration { + key: previous_signing_key, + version: existing_key.current.version, + }), + }; + + context + .set_state_entry(keys.get_address(), serde_json::to_string(&keys)?.into_bytes())?; + + context.add_event( + "opa/operation".to_string(), + vec![("transaction_id".to_string(), request.signature.clone())], + &opa_event(1, keys.into())?, + )?; + + Ok(()) + }, + opa_tp_protocol::messages::signed_operation::payload::Operation::SetPolicy( + opa_tp_protocol::messages::SetPolicy { policy, id }, + ) => { + let _existing_policy_meta = context.get_state_entry(&policy_meta_address(&*id))?; + let hash = Sha256::digest(&policy); + let hash = hex::encode(hash); + + let policy_meta = + PolicyMeta { id: id.clone(), hash, policy_address: policy_address(&*id) }; + + context.set_state_entry( + policy_meta_address(&id), + serde_json::to_string(&policy_meta)?.into_bytes(), + )?; + + context.set_state_entry(policy_address(&id), policy)?; + + context.add_event( + "opa/operation".to_string(), + vec![("transaction_id".to_string(), request.signature.clone())], + &opa_event(1, policy_meta.into())?, + )?; + + Ok(()) + }, + _ => Err(OpaTpError::MalformedMessage), + } } fn root_keys_from_state( - _request: &TpProcessRequest, - context: &dyn TransactionContext, + _request: &TpProcessRequest, + context: &dyn TransactionContext, ) -> Result, OpaTpError> { - let existing_key = context.get_state_entry(&key_address("root"))?; + let existing_key = context.get_state_entry(&key_address("root"))?; - if let Some(existing_key) = existing_key { - let existing_key: Keys = serde_json::from_str( - from_utf8(&existing_key).map_err(|_| OpaTpError::MalformedMessage)?, - )?; + if let Some(existing_key) = existing_key { + let existing_key: Keys = serde_json::from_str( + from_utf8(&existing_key).map_err(|_| OpaTpError::MalformedMessage)?, + )?; - Ok(Some(existing_key)) - } else { - Ok(None) - } + Ok(Some(existing_key)) + } else { + Ok(None) + } } impl TP for OpaTransactionHandler { - #[instrument(skip(request, context))] - fn apply( - &self, - request: &TpProcessRequest, - context: &mut dyn TransactionContext, - ) -> Result<(), ApplyError> { - let payload = request.get_payload(); - let submission = Submission::decode(payload).map_err(|err| { - ApplyError::InvalidTransaction(format!("Failed to parse payload: {err}")) - })?; - - debug!(signed_operation = ?submission); - - let process: Result<_, OpaTpError> = (|| { - verify_signed_operation(&submission, &root_keys_from_state(request, context)?)?; - - apply_signed_operation(submission.payload.unwrap(), request, context)?; - Ok(()) - })(); - - // Protocol errors are non resumable, but operation state / signing - // errors may possibly be resumable. - match process { - Ok(_) => Ok(()), - Err(e @ OpaTpError::MalformedMessage) => Err(ApplyError::InternalError(e.to_string())), - Err(e @ OpaTpError::JsonSerialize(_)) => Err(ApplyError::InternalError(e.to_string())), - Err(e) => Ok(context.add_event( - "opa/operation".to_string(), - vec![("transaction_id".to_string(), request.signature.clone())], - &opa_event(1, OpaOperationEvent::Error(e.to_string())) - .map_err(|e| ApplyError::InternalError(e.to_string()))?, - )?), - } - } + #[instrument(skip(request, context))] + fn apply( + &self, + request: &TpProcessRequest, + context: &mut dyn TransactionContext, + ) -> Result<(), ApplyError> { + let payload = request.get_payload(); + let submission = Submission::decode(payload).map_err(|err| { + ApplyError::InvalidTransaction(format!("Failed to parse payload: {err}")) + })?; + + debug!(signed_operation = ?submission); + + let process: Result<_, OpaTpError> = (|| { + verify_signed_operation(&submission, &root_keys_from_state(request, context)?)?; + + apply_signed_operation(submission.payload.unwrap(), request, context)?; + Ok(()) + })(); + + // Protocol errors are non resumable, but operation state / signing + // errors may possibly be resumable. + match process { + Ok(_) => Ok(()), + Err(e @ OpaTpError::MalformedMessage) => Err(ApplyError::InternalError(e.to_string())), + Err(e @ OpaTpError::JsonSerialize(_)) => Err(ApplyError::InternalError(e.to_string())), + Err(e) => Ok(context.add_event( + "opa/operation".to_string(), + vec![("transaction_id".to_string(), request.signature.clone())], + &opa_event(1, OpaOperationEvent::Error(e.to_string())) + .map_err(|e| ApplyError::InternalError(e.to_string()))?, + )?), + } + } } impl TransactionHandler for OpaTransactionHandler { - fn family_name(&self) -> String { - self.family_name.clone() - } + fn family_name(&self) -> String { + self.family_name.clone() + } - fn family_versions(&self) -> Vec { - self.family_versions.clone() - } + fn family_versions(&self) -> Vec { + self.family_versions.clone() + } - fn namespaces(&self) -> Vec { - self.namespaces.clone() - } + fn namespaces(&self) -> Vec { + self.namespaces.clone() + } - #[instrument( + #[instrument( skip(request,context), fields( transaction_id = %request.signature, @@ -407,267 +386,259 @@ impl TransactionHandler for OpaTransactionHandler { dependencies = ?request.header.as_ref().map(|x| &x.dependencies) ) )] - fn apply( - &self, - request: &TpProcessRequest, - context: &mut dyn TransactionContext, - ) -> Result<(), ApplyError> { - TP::apply(self, request, context) - } + fn apply( + &self, + request: &TpProcessRequest, + context: &mut dyn TransactionContext, + ) -> Result<(), ApplyError> { + TP::apply(self, request, context) + } } #[cfg(test)] mod test { - use async_stl_client::sawtooth::MessageBuilder; - use chronicle_signing::{ - chronicle_secret_names, BatcherKnownKeyNamesSigner, ChronicleSigning, - OpaKnownKeyNamesSigner, BATCHER_NAMESPACE, CHRONICLE_NAMESPACE, OPA_NAMESPACE, OPA_PK, - }; - use k256::{ecdsa::SigningKey, SecretKey}; - use opa_tp_protocol::{ - address, - messages::{OpaEvent, Submission}, - state::key_address, - submission::SubmissionBuilder, - }; - use prost::Message; - use rand::rngs::StdRng; - use rand_core::SeedableRng; - use sawtooth_sdk::{ - messages::{processor::TpProcessRequest, transaction::TransactionHeader}, - processor::handler::{ContextError, TransactionContext}, - }; - use serde_json::Value; - use std::{cell::RefCell, collections::BTreeMap}; - - use crate::abstract_tp::TP; - - use super::OpaTransactionHandler; - - type TestTxEvents = Vec<(String, Vec<(String, String)>, Vec)>; - - #[derive(Clone)] - pub struct TestTransactionContext { - pub state: RefCell>>, - pub events: RefCell, - } - - type PrintableEvent = Vec<(String, Vec<(String, String)>, Value)>; - - impl TestTransactionContext { - pub fn new() -> Self { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - Self { - state: RefCell::new(BTreeMap::new()), - events: RefCell::new(vec![]), - } - } - - /// Returns a list of tuples representing the readable state of this `TestTransactionContext`. - /// - /// The method first converts raw byte strings into JSON objects for meta data and keys. - pub fn readable_state(&self) -> Vec<(String, Value)> { - // Deal with the fact that policies are raw bytes, but meta data and - // keys are json - self.state - .borrow() - .iter() - .map(|(k, v)| { - let as_string = String::from_utf8(v.clone()).unwrap(); - match serde_json::from_str(&as_string) { - Ok(json) => (k.clone(), json), - Err(_) => (k.clone(), serde_json::to_value(v.clone()).unwrap()), - } - }) - .collect() - } - - /// Returns the events as a vector of PrintableEvent structs. - pub fn readable_events(&self) -> PrintableEvent { - self.events - .borrow() - .iter() - .map(|(k, attr, data)| { - ( - k.clone(), - attr.clone(), - match &OpaEvent::decode(&**data).unwrap().payload.unwrap() { - opa_tp_protocol::messages::opa_event::Payload::Operation(operation) => { - serde_json::from_str(operation).unwrap() - } - opa_tp_protocol::messages::opa_event::Payload::Error(error) => { - serde_json::from_str(error).unwrap() - } - }, - ) - }) - .collect() - } - } - - impl TransactionContext for TestTransactionContext { - fn add_receipt_data( - self: &TestTransactionContext, - _data: &[u8], - ) -> Result<(), ContextError> { - unimplemented!() - } - - fn add_event( - self: &TestTransactionContext, - event_type: String, - attributes: Vec<(String, String)>, - data: &[u8], - ) -> Result<(), ContextError> { - self.events - .borrow_mut() - .push((event_type, attributes, data.to_vec())); - Ok(()) - } - - fn delete_state_entries( - self: &TestTransactionContext, - _addresses: &[std::string::String], - ) -> Result, ContextError> { - unimplemented!() - } - - fn get_state_entries( - &self, - addresses: &[String], - ) -> Result)>, ContextError> { - Ok(self - .state - .borrow() - .iter() - .filter(|(k, _)| addresses.contains(k)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect()) - } - - fn set_state_entries( - self: &TestTransactionContext, - entries: Vec<(String, Vec)>, - ) -> std::result::Result<(), sawtooth_sdk::processor::handler::ContextError> { - for entry in entries { - self.state.borrow_mut().insert(entry.0, entry.1); - } - - Ok(()) - } - } - - fn key_from_seed(seed: u8) -> SigningKey { - let secret: SigningKey = SecretKey::random(StdRng::from_seed([seed; 32])).into(); - - secret - } - - /// Apply a transaction to a transaction context - async fn apply_tx( - mut context: TestTransactionContext, - addresses: &[String], - submission: &Submission, - signer: &ChronicleSigning, - ) -> TestTransactionContext { - let message_builder = MessageBuilder::new_deterministic(address::FAMILY, address::VERSION); - let (tx, id) = message_builder - .make_sawtooth_transaction( - addresses.to_vec(), - addresses.to_vec(), - vec![], - submission, - signer.batcher_verifying().await.unwrap(), - |bytes| { - let signer = signer.clone(); - let bytes = bytes.to_vec(); - async move { signer.batcher_sign(&bytes).await } - }, - ) - .await - .unwrap(); - let processor = OpaTransactionHandler::new(); - let header = - ::parse_from_bytes(&tx.header).unwrap(); - let mut request = TpProcessRequest::default(); - request.set_header(header); - request.set_payload(tx.payload); - request.set_signature(id.as_str().to_owned()); - processor.apply(&request, &mut context).unwrap(); - context - } - - /// Assert that all contexts in the given slice are equal - fn assert_contexts_are_equal(contexts: &[TestTransactionContext]) { - // get the first context in the slice - let first_context = &contexts[0]; - - // check that all contexts have the same readable state and events - assert!( - contexts.iter().all(|context| { - ( - first_context.readable_state(), - first_context.readable_events(), - ) == (context.readable_state(), context.readable_events()) - }), - "All contexts must be the same" - ); - } - - /// Applies a transaction `submission` to `context` using `batcher_key`, - /// `number_of_determinism_checking_cycles` times, checking for determinism between - /// each cycle. - async fn submission_to_state( - context: TestTransactionContext, - signer: ChronicleSigning, - addresses: &[String], - submission: Submission, - ) -> TestTransactionContext { - // Set the number of determinism checking cycles - let number_of_determinism_checking_cycles = 5; - - // Get the current state and events before applying the transactions. - let preprocessing_state_and_events = - { (context.readable_state(), context.readable_events()) }; - - // Create a vector of `number_of_determinism_checking_cycles` contexts - let contexts = vec![context; number_of_determinism_checking_cycles]; - - let mut results = Vec::with_capacity(number_of_determinism_checking_cycles); - - for context in contexts { - let result = apply_tx(context, addresses, &submission, &signer).await; - results.push(result); - } - - // Check that the context has been updated after running `apply_tx` - let updated_readable_state_and_events = { - let context = results.last().unwrap(); - (context.readable_state(), context.readable_events()) - }; - assert_ne!( - preprocessing_state_and_events, updated_readable_state_and_events, - "Context must be updated after running apply" - ); - - // Check if all contexts are the same after running `apply_tx` - assert_contexts_are_equal(&results); - - // Return the last context from the vector of contexts - results.pop().unwrap() - } - - #[tokio::test] - async fn bootstrap_from_initial_state() { - let secrets = chronicle_signing().await; - let context = TestTransactionContext::new(); - let builder = SubmissionBuilder::bootstrap_root(secrets.opa_verifying().await.unwrap()); - let submission = builder.build(0xffff); - - let context = - submission_to_state(context, secrets, &[key_address("root")], submission).await; - - insta::assert_yaml_snapshot!(context.readable_state(), { + use async_stl_client::sawtooth::MessageBuilder; + use chronicle_signing::{ + chronicle_secret_names, BatcherKnownKeyNamesSigner, ChronicleSigning, + OpaKnownKeyNamesSigner, BATCHER_NAMESPACE, CHRONICLE_NAMESPACE, OPA_NAMESPACE, OPA_PK, + }; + use k256::{ecdsa::SigningKey, SecretKey}; + use opa_tp_protocol::{ + address, + messages::{OpaEvent, Submission}, + state::key_address, + submission::SubmissionBuilder, + }; + use prost::Message; + use rand::rngs::StdRng; + use rand_core::SeedableRng; + use sawtooth_sdk::{ + messages::{processor::TpProcessRequest, transaction::TransactionHeader}, + processor::handler::{ContextError, TransactionContext}, + }; + use serde_json::Value; + use std::{cell::RefCell, collections::BTreeMap}; + + use crate::abstract_tp::TP; + + use super::OpaTransactionHandler; + + type TestTxEvents = Vec<(String, Vec<(String, String)>, Vec)>; + + #[derive(Clone)] + pub struct TestTransactionContext { + pub state: RefCell>>, + pub events: RefCell, + } + + type PrintableEvent = Vec<(String, Vec<(String, String)>, Value)>; + + impl TestTransactionContext { + pub fn new() -> Self { + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + Self { state: RefCell::new(BTreeMap::new()), events: RefCell::new(vec![]) } + } + + /// Returns a list of tuples representing the readable state of this + /// `TestTransactionContext`. + /// + /// The method first converts raw byte strings into JSON objects for meta data and keys. + pub fn readable_state(&self) -> Vec<(String, Value)> { + // Deal with the fact that policies are raw bytes, but meta data and + // keys are json + self.state + .borrow() + .iter() + .map(|(k, v)| { + let as_string = String::from_utf8(v.clone()).unwrap(); + match serde_json::from_str(&as_string) { + Ok(json) => (k.clone(), json), + Err(_) => (k.clone(), serde_json::to_value(v.clone()).unwrap()), + } + }) + .collect() + } + + /// Returns the events as a vector of PrintableEvent structs. + pub fn readable_events(&self) -> PrintableEvent { + self.events + .borrow() + .iter() + .map(|(k, attr, data)| { + ( + k.clone(), + attr.clone(), + match &OpaEvent::decode(&**data).unwrap().payload.unwrap() { + opa_tp_protocol::messages::opa_event::Payload::Operation(operation) => + serde_json::from_str(operation).unwrap(), + opa_tp_protocol::messages::opa_event::Payload::Error(error) => + serde_json::from_str(error).unwrap(), + }, + ) + }) + .collect() + } + } + + impl TransactionContext for TestTransactionContext { + fn add_receipt_data( + self: &TestTransactionContext, + _data: &[u8], + ) -> Result<(), ContextError> { + unimplemented!() + } + + fn add_event( + self: &TestTransactionContext, + event_type: String, + attributes: Vec<(String, String)>, + data: &[u8], + ) -> Result<(), ContextError> { + self.events.borrow_mut().push((event_type, attributes, data.to_vec())); + Ok(()) + } + + fn delete_state_entries( + self: &TestTransactionContext, + _addresses: &[std::string::String], + ) -> Result, ContextError> { + unimplemented!() + } + + fn get_state_entries( + &self, + addresses: &[String], + ) -> Result)>, ContextError> { + Ok(self + .state + .borrow() + .iter() + .filter(|(k, _)| addresses.contains(k)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect()) + } + + fn set_state_entries( + self: &TestTransactionContext, + entries: Vec<(String, Vec)>, + ) -> std::result::Result<(), sawtooth_sdk::processor::handler::ContextError> { + for entry in entries { + self.state.borrow_mut().insert(entry.0, entry.1); + } + + Ok(()) + } + } + + fn key_from_seed(seed: u8) -> SigningKey { + let secret: SigningKey = SecretKey::random(StdRng::from_seed([seed; 32])).into(); + + secret + } + + /// Apply a transaction to a transaction context + async fn apply_tx( + mut context: TestTransactionContext, + addresses: &[String], + submission: &Submission, + signer: &ChronicleSigning, + ) -> TestTransactionContext { + let message_builder = MessageBuilder::new_deterministic(address::FAMILY, address::VERSION); + let (tx, id) = message_builder + .make_sawtooth_transaction( + addresses.to_vec(), + addresses.to_vec(), + vec![], + submission, + signer.batcher_verifying().await.unwrap(), + |bytes| { + let signer = signer.clone(); + let bytes = bytes.to_vec(); + async move { signer.batcher_sign(&bytes).await } + }, + ) + .await + .unwrap(); + let processor = OpaTransactionHandler::new(); + let header = + ::parse_from_bytes(&tx.header).unwrap(); + let mut request = TpProcessRequest::default(); + request.set_header(header); + request.set_payload(tx.payload); + request.set_signature(id.as_str().to_owned()); + processor.apply(&request, &mut context).unwrap(); + context + } + + /// Assert that all contexts in the given slice are equal + fn assert_contexts_are_equal(contexts: &[TestTransactionContext]) { + // get the first context in the slice + let first_context = &contexts[0]; + + // check that all contexts have the same readable state and events + assert!( + contexts.iter().all(|context| { + (first_context.readable_state(), first_context.readable_events()) == + (context.readable_state(), context.readable_events()) + }), + "All contexts must be the same" + ); + } + + /// Applies a transaction `submission` to `context` using `batcher_key`, + /// `number_of_determinism_checking_cycles` times, checking for determinism between + /// each cycle. + async fn submission_to_state( + context: TestTransactionContext, + signer: ChronicleSigning, + addresses: &[String], + submission: Submission, + ) -> TestTransactionContext { + // Set the number of determinism checking cycles + let number_of_determinism_checking_cycles = 5; + + // Get the current state and events before applying the transactions. + let preprocessing_state_and_events = + { (context.readable_state(), context.readable_events()) }; + + // Create a vector of `number_of_determinism_checking_cycles` contexts + let contexts = vec![context; number_of_determinism_checking_cycles]; + + let mut results = Vec::with_capacity(number_of_determinism_checking_cycles); + + for context in contexts { + let result = apply_tx(context, addresses, &submission, &signer).await; + results.push(result); + } + + // Check that the context has been updated after running `apply_tx` + let updated_readable_state_and_events = { + let context = results.last().unwrap(); + (context.readable_state(), context.readable_events()) + }; + assert_ne!( + preprocessing_state_and_events, updated_readable_state_and_events, + "Context must be updated after running apply" + ); + + // Check if all contexts are the same after running `apply_tx` + assert_contexts_are_equal(&results); + + // Return the last context from the vector of contexts + results.pop().unwrap() + } + + #[tokio::test] + async fn bootstrap_from_initial_state() { + let secrets = chronicle_signing().await; + let context = TestTransactionContext::new(); + let builder = SubmissionBuilder::bootstrap_root(secrets.opa_verifying().await.unwrap()); + let submission = builder.build(0xffff); + + let context = + submission_to_state(context, secrets, &[key_address("root")], submission).await; + + insta::assert_yaml_snapshot!(context.readable_state(), { ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -680,7 +651,7 @@ mod test { expired: ~ id: root "###); - insta::assert_yaml_snapshot!(context.readable_events(), { + insta::assert_yaml_snapshot!(context.readable_events(), { ".**.date" => "[date]", }, @r###" --- @@ -694,76 +665,76 @@ mod test { expired: ~ id: root "###); - } - - /// Needed for further tests - async fn bootstrap_root() -> (TestTransactionContext, ChronicleSigning) { - let secrets = chronicle_signing().await; - - let context = TestTransactionContext::new(); - let builder = SubmissionBuilder::bootstrap_root(secrets.opa_verifying().await.unwrap()); - let submission = builder.build(0xffff); - - ( - submission_to_state(context, secrets.clone(), &[key_address("root")], submission).await, - secrets, - ) - } - - async fn chronicle_signing() -> ChronicleSigning { - let mut names = chronicle_secret_names(); - names.append(&mut vec![ - (CHRONICLE_NAMESPACE.to_string(), "rotate_root".to_string()), - (CHRONICLE_NAMESPACE.to_string(), "non_root_1".to_string()), - (CHRONICLE_NAMESPACE.to_string(), "non_root_2".to_string()), - (CHRONICLE_NAMESPACE.to_string(), "key_1".to_string()), - (CHRONICLE_NAMESPACE.to_string(), "key_2".to_string()), - (CHRONICLE_NAMESPACE.to_string(), "opa-pk".to_string()), - ]); - - names.append(&mut vec![ - (OPA_NAMESPACE.to_string(), "rotate_root".to_string()), - (OPA_NAMESPACE.to_string(), "non_root_1".to_string()), - (OPA_NAMESPACE.to_string(), "non_root_2".to_string()), - (OPA_NAMESPACE.to_string(), "key_1".to_string()), - (OPA_NAMESPACE.to_string(), "key_2".to_string()), - (OPA_NAMESPACE.to_string(), "opa-pk".to_string()), - (OPA_NAMESPACE.to_string(), "new_root_1".to_string()), - ]); - - ChronicleSigning::new( - names, - vec![ - ( - CHRONICLE_NAMESPACE.to_string(), - chronicle_signing::ChronicleSecretsOptions::test_keys(), - ), - ( - OPA_NAMESPACE.to_string(), - chronicle_signing::ChronicleSecretsOptions::test_keys(), - ), - ( - BATCHER_NAMESPACE.to_string(), - chronicle_signing::ChronicleSecretsOptions::test_keys(), - ), - ], - ) - .await - .unwrap() - } - - #[tokio::test] - async fn rotate_root() { - let (context, signing) = bootstrap_root().await; - let builder = SubmissionBuilder::rotate_key("root", &signing, "opa-pk", "new_root_1") - .await - .unwrap(); - let submission = builder.build(0xffff); - - let context = - submission_to_state(context, signing, &[key_address("root")], submission).await; - - insta::assert_yaml_snapshot!(context.readable_state(),{ + } + + /// Needed for further tests + async fn bootstrap_root() -> (TestTransactionContext, ChronicleSigning) { + let secrets = chronicle_signing().await; + + let context = TestTransactionContext::new(); + let builder = SubmissionBuilder::bootstrap_root(secrets.opa_verifying().await.unwrap()); + let submission = builder.build(0xffff); + + ( + submission_to_state(context, secrets.clone(), &[key_address("root")], submission).await, + secrets, + ) + } + + async fn chronicle_signing() -> ChronicleSigning { + let mut names = chronicle_secret_names(); + names.append(&mut vec![ + (CHRONICLE_NAMESPACE.to_string(), "rotate_root".to_string()), + (CHRONICLE_NAMESPACE.to_string(), "non_root_1".to_string()), + (CHRONICLE_NAMESPACE.to_string(), "non_root_2".to_string()), + (CHRONICLE_NAMESPACE.to_string(), "key_1".to_string()), + (CHRONICLE_NAMESPACE.to_string(), "key_2".to_string()), + (CHRONICLE_NAMESPACE.to_string(), "opa-pk".to_string()), + ]); + + names.append(&mut vec![ + (OPA_NAMESPACE.to_string(), "rotate_root".to_string()), + (OPA_NAMESPACE.to_string(), "non_root_1".to_string()), + (OPA_NAMESPACE.to_string(), "non_root_2".to_string()), + (OPA_NAMESPACE.to_string(), "key_1".to_string()), + (OPA_NAMESPACE.to_string(), "key_2".to_string()), + (OPA_NAMESPACE.to_string(), "opa-pk".to_string()), + (OPA_NAMESPACE.to_string(), "new_root_1".to_string()), + ]); + + ChronicleSigning::new( + names, + vec![ + ( + CHRONICLE_NAMESPACE.to_string(), + chronicle_signing::ChronicleSecretsOptions::test_keys(), + ), + ( + OPA_NAMESPACE.to_string(), + chronicle_signing::ChronicleSecretsOptions::test_keys(), + ), + ( + BATCHER_NAMESPACE.to_string(), + chronicle_signing::ChronicleSecretsOptions::test_keys(), + ), + ], + ) + .await + .unwrap() + } + + #[tokio::test] + async fn rotate_root() { + let (context, signing) = bootstrap_root().await; + let builder = SubmissionBuilder::rotate_key("root", &signing, "opa-pk", "new_root_1") + .await + .unwrap(); + let submission = builder.build(0xffff); + + let context = + submission_to_state(context, signing, &[key_address("root")], submission).await; + + insta::assert_yaml_snapshot!(context.readable_state(),{ ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -780,7 +751,7 @@ mod test { id: root "### ); - insta::assert_yaml_snapshot!(context.readable_events(), { + insta::assert_yaml_snapshot!(context.readable_events(), { ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -807,21 +778,21 @@ mod test { version: 0 id: root "### ); - } + } - #[tokio::test] - async fn register_valid_key() { - let (context, signing) = bootstrap_root().await; + #[tokio::test] + async fn register_valid_key() { + let (context, signing) = bootstrap_root().await; - let builder = SubmissionBuilder::register_key("nonroot", "opa-pk", &signing, false) - .await - .unwrap(); - let submission = builder.build(0xffff); + let builder = SubmissionBuilder::register_key("nonroot", "opa-pk", &signing, false) + .await + .unwrap(); + let submission = builder.build(0xffff); - let context = - submission_to_state(context, signing, &[key_address("nonroot")], submission).await; + let context = + submission_to_state(context, signing, &[key_address("nonroot")], submission).await; - insta::assert_yaml_snapshot!(context.readable_state(),{ + insta::assert_yaml_snapshot!(context.readable_state(),{ ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -840,7 +811,7 @@ mod test { expired: ~ id: nonroot "### ); - insta::assert_yaml_snapshot!(context.readable_events(), { + insta::assert_yaml_snapshot!(context.readable_events(), { ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -865,36 +836,32 @@ mod test { expired: ~ id: nonroot "###); - } - - #[tokio::test] - async fn rotate_valid_key() { - let (context, signing) = bootstrap_root().await; - let _non_root_key = key_from_seed(1); - - let builder = SubmissionBuilder::register_key("nonroot", "key_1", &signing, false) - .await - .unwrap(); - let submission = builder.build(0xffff); - - let context = submission_to_state( - context, - signing.clone(), - &[key_address("nonroot")], - submission, - ) - .await; + } + + #[tokio::test] + async fn rotate_valid_key() { + let (context, signing) = bootstrap_root().await; + let _non_root_key = key_from_seed(1); + + let builder = SubmissionBuilder::register_key("nonroot", "key_1", &signing, false) + .await + .unwrap(); + let submission = builder.build(0xffff); + + let context = + submission_to_state(context, signing.clone(), &[key_address("nonroot")], submission) + .await; - let _new_non_root = key_from_seed(2); - let builder = SubmissionBuilder::rotate_key("nonroot", &signing, "key_1", "key_2") - .await - .unwrap(); - let submission = builder.build(0xffff); + let _new_non_root = key_from_seed(2); + let builder = SubmissionBuilder::rotate_key("nonroot", &signing, "key_1", "key_2") + .await + .unwrap(); + let submission = builder.build(0xffff); - let context = - submission_to_state(context, signing, &[key_address("nonroot")], submission).await; + let context = + submission_to_state(context, signing, &[key_address("nonroot")], submission).await; - insta::assert_yaml_snapshot!(context.readable_state(),{ + insta::assert_yaml_snapshot!(context.readable_state(),{ ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -915,7 +882,7 @@ mod test { version: 0 id: nonroot "### ); - insta::assert_yaml_snapshot!(context.readable_events(), { + insta::assert_yaml_snapshot!(context.readable_events(), { ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -951,22 +918,22 @@ mod test { version: 0 id: nonroot "###); - } + } - #[tokio::test] - async fn cannot_register_nonroot_key_as_root() { - let (context, root_key) = bootstrap_root().await; - let _non_root_key = key_from_seed(1); + #[tokio::test] + async fn cannot_register_nonroot_key_as_root() { + let (context, root_key) = bootstrap_root().await; + let _non_root_key = key_from_seed(1); - let builder = SubmissionBuilder::register_key("root", "key_1", &root_key, false) - .await - .unwrap(); - let submission = builder.build(0xffff); + let builder = SubmissionBuilder::register_key("root", "key_1", &root_key, false) + .await + .unwrap(); + let submission = builder.build(0xffff); - let context = - submission_to_state(context, root_key, &[key_address("nonroot")], submission).await; + let context = + submission_to_state(context, root_key, &[key_address("nonroot")], submission).await; - insta::assert_yaml_snapshot!(context.readable_state(),{ + insta::assert_yaml_snapshot!(context.readable_state(),{ ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -979,7 +946,7 @@ mod test { expired: ~ id: root "### ); - insta::assert_yaml_snapshot!(context.readable_events(), { + insta::assert_yaml_snapshot!(context.readable_events(), { ".**.date" => "[date]", }, @r###" --- @@ -997,21 +964,20 @@ mod test { - 20da702eb32cb0af245752bea18b8878759282c3abd8e8334a08caae1711a896087056042c7f9d6e5d818ceef0767d0099383f82758003601513ff7356f9c24d - error: Invalid operation "###); - } + } - #[tokio::test] - async fn cannot_register_key_as_nonroot_with_overwrite() { - let (context, root_key) = bootstrap_root().await; + #[tokio::test] + async fn cannot_register_key_as_nonroot_with_overwrite() { + let (context, root_key) = bootstrap_root().await; - let builder = SubmissionBuilder::register_key("root", "key_1", &root_key, true) - .await - .unwrap(); - let submission = builder.build(0xffff); + let builder = + SubmissionBuilder::register_key("root", "key_1", &root_key, true).await.unwrap(); + let submission = builder.build(0xffff); - let context = - submission_to_state(context, root_key, &[key_address("nonroot")], submission).await; + let context = + submission_to_state(context, root_key, &[key_address("nonroot")], submission).await; - insta::assert_yaml_snapshot!(context.readable_state(),{ + insta::assert_yaml_snapshot!(context.readable_state(),{ ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -1024,7 +990,7 @@ mod test { expired: ~ id: root "### ); - insta::assert_yaml_snapshot!(context.readable_events(), { + insta::assert_yaml_snapshot!(context.readable_events(), { ".**.date" => "[date]", }, @r###" --- @@ -1042,34 +1008,30 @@ mod test { - a31aafdbde8b242beace8c62a8d13f6de4c853b6257426e962598a53dcab35a81c3b424b0da8cef52092a8bc0d036c54617f793a0ab29f7b4f235176525ffc03 - error: Invalid operation "###); - } - - #[tokio::test] - async fn cannot_register_existing_key() { - let (context, signing) = bootstrap_root().await; - - let builder = SubmissionBuilder::register_key("nonroot", "key_1", &signing, false) - .await - .unwrap(); - let submission = builder.build(0xffff); - - let context = submission_to_state( - context, - signing.clone(), - &[key_address("nonroot")], - submission, - ) - .await; + } + + #[tokio::test] + async fn cannot_register_existing_key() { + let (context, signing) = bootstrap_root().await; + + let builder = SubmissionBuilder::register_key("nonroot", "key_1", &signing, false) + .await + .unwrap(); + let submission = builder.build(0xffff); + + let context = + submission_to_state(context, signing.clone(), &[key_address("nonroot")], submission) + .await; - let builder = SubmissionBuilder::register_key("nonroot", "key_1", &signing, false) - .await - .unwrap(); - let submission = builder.build(0xffff); + let builder = SubmissionBuilder::register_key("nonroot", "key_1", &signing, false) + .await + .unwrap(); + let submission = builder.build(0xffff); - let context = - submission_to_state(context, signing, &[key_address("nonroot")], submission).await; + let context = + submission_to_state(context, signing, &[key_address("nonroot")], submission).await; - insta::assert_yaml_snapshot!(context.readable_state(),{ + insta::assert_yaml_snapshot!(context.readable_state(),{ ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -1088,7 +1050,7 @@ mod test { expired: ~ id: nonroot "### ); - insta::assert_yaml_snapshot!(context.readable_events(), { + insta::assert_yaml_snapshot!(context.readable_events(), { ".**.date" => "[date]", }, @r###" --- @@ -1115,35 +1077,31 @@ mod test { - d88103ab8f4aecbf9d3dd399b2fd5d1ab531d8c312937a2a13e6ca87dffaeaf07d7b5a0a54f902587abdf7c2180fbe525cd7ad28f86718ae8c87c9df7fc2582f - error: Invalid operation "###); - } - - #[tokio::test] - async fn can_register_existing_key_with_overwrite() { - let (context, root_key) = bootstrap_root().await; - let _non_root_key = key_from_seed(1); - - let builder = SubmissionBuilder::register_key("nonroot", "key_1", &root_key, false) - .await - .unwrap(); - let submission = builder.build(0xffff); - - let context = submission_to_state( - context, - root_key.clone(), - &[key_address("nonroot")], - submission, - ) - .await; + } + + #[tokio::test] + async fn can_register_existing_key_with_overwrite() { + let (context, root_key) = bootstrap_root().await; + let _non_root_key = key_from_seed(1); + + let builder = SubmissionBuilder::register_key("nonroot", "key_1", &root_key, false) + .await + .unwrap(); + let submission = builder.build(0xffff); + + let context = + submission_to_state(context, root_key.clone(), &[key_address("nonroot")], submission) + .await; - let builder = SubmissionBuilder::register_key("nonroot", "key_1", &root_key, true) - .await - .unwrap(); - let submission = builder.build(0xffff); + let builder = SubmissionBuilder::register_key("nonroot", "key_1", &root_key, true) + .await + .unwrap(); + let submission = builder.build(0xffff); - let context = - submission_to_state(context, root_key, &[key_address("nonroot")], submission).await; + let context = + submission_to_state(context, root_key, &[key_address("nonroot")], submission).await; - insta::assert_yaml_snapshot!(context.readable_state(),{ + insta::assert_yaml_snapshot!(context.readable_state(),{ ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -1162,7 +1120,7 @@ mod test { expired: ~ id: nonroot "### ); - insta::assert_yaml_snapshot!(context.readable_events(), { + insta::assert_yaml_snapshot!(context.readable_events(), { ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -1196,21 +1154,20 @@ mod test { expired: ~ id: nonroot "###); - } + } - #[tokio::test] - async fn cannot_register_existing_root_key_with_overwrite() { - let (context, signing) = bootstrap_root().await; + #[tokio::test] + async fn cannot_register_existing_root_key_with_overwrite() { + let (context, signing) = bootstrap_root().await; - let builder = SubmissionBuilder::register_key("root", OPA_PK, &signing, true) - .await - .unwrap(); - let submission = builder.build(0xffff); + let builder = + SubmissionBuilder::register_key("root", OPA_PK, &signing, true).await.unwrap(); + let submission = builder.build(0xffff); - let context = - submission_to_state(context, signing, &[key_address("root")], submission).await; + let context = + submission_to_state(context, signing, &[key_address("root")], submission).await; - insta::assert_yaml_snapshot!(context.readable_state(),{ + insta::assert_yaml_snapshot!(context.readable_state(),{ ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -1223,7 +1180,7 @@ mod test { expired: ~ id: root "### ); - insta::assert_yaml_snapshot!(context.readable_events(), { + insta::assert_yaml_snapshot!(context.readable_events(), { ".**.date" => "[date]", }, @r###" --- @@ -1241,23 +1198,22 @@ mod test { - bd3395015bfe80530016a9ec49e4c47037da397e9992bff9d9e450361e4d5ec871d2cd23fd26bdc3687c81ddca05220608c159962f9cd57192c6d33cd906fc4c - error: Invalid operation "###); - } + } - #[tokio::test] - async fn set_a_policy() { - let (context, signing) = bootstrap_root().await; + #[tokio::test] + async fn set_a_policy() { + let (context, signing) = bootstrap_root().await; - // Policies can only be set by the root key owner - let builder = SubmissionBuilder::set_policy("test", vec![0, 1, 2, 3], &signing) - .await - .unwrap(); + // Policies can only be set by the root key owner + let builder = + SubmissionBuilder::set_policy("test", vec![0, 1, 2, 3], &signing).await.unwrap(); - let submission = builder.build(0xffff); + let submission = builder.build(0xffff); - let context = - submission_to_state(context, signing, &[key_address("nonroot")], submission).await; + let context = + submission_to_state(context, signing, &[key_address("nonroot")], submission).await; - insta::assert_yaml_snapshot!(context.readable_state(),{ + insta::assert_yaml_snapshot!(context.readable_state(),{ ".**.date" => "[date]", ".**.transaction_id" => "[hash]", ".**.key" => "[pem]", @@ -1280,7 +1236,7 @@ mod test { policy_address: 7ed1931c262a4be700b69974438a35ae56a07ce96778b276c8a061dc254d9862c7ecff "###); - insta::assert_yaml_snapshot!(context.readable_events(),{ + insta::assert_yaml_snapshot!(context.readable_events(),{ ".**.date" => "[date]", }, @r###" --- @@ -1301,5 +1257,5 @@ mod test { id: test policy_address: 7ed1931c262a4be700b69974438a35ae56a07ce96778b276c8a061dc254d9862c7ecff "###); - } + } } diff --git a/crates/opactl/build.rs b/crates/opactl/build.rs index 5a1d86bbe..afb2c9546 100644 --- a/crates/opactl/build.rs +++ b/crates/opactl/build.rs @@ -1,8 +1,8 @@ fn main() { - //Create a .VERSION file containing 'local' if it does not exist + //Create a .VERSION file containing 'local' if it does not exist - let version_file = std::path::Path::new("../../.VERSION"); - if !version_file.exists() { - std::fs::write(version_file, "local").expect("Unable to write file"); - } + let version_file = std::path::Path::new("../../.VERSION"); + if !version_file.exists() { + std::fs::write(version_file, "local").expect("Unable to write file"); + } } diff --git a/crates/opactl/src/cli.rs b/crates/opactl/src/cli.rs index 499e83141..b081dd979 100644 --- a/crates/opactl/src/cli.rs +++ b/crates/opactl/src/cli.rs @@ -1,12 +1,12 @@ use std::path::PathBuf; use chronicle_signing::{ - opa_secret_names, ChronicleSecretsOptions, ChronicleSigning, SecretError, BATCHER_NAMESPACE, - OPA_NAMESPACE, + opa_secret_names, ChronicleSecretsOptions, ChronicleSigning, SecretError, BATCHER_NAMESPACE, + OPA_NAMESPACE, }; use clap::{ - builder::{NonEmptyStringValueParser, StringValueParser}, - Arg, ArgAction, ArgMatches, Command, ValueHint, + builder::{NonEmptyStringValueParser, StringValueParser}, + Arg, ArgAction, ArgMatches, Command, ValueHint, }; use tracing::info; @@ -14,161 +14,157 @@ use url::Url; // Generate an ephemeral key if no key is provided fn batcher_key() -> Arg { - Arg::new( "batcher-key-from-store") + Arg::new( "batcher-key-from-store") .long("batcher-key-from-store") .num_args(0) .help("If specified the key 'batcher-pk' will be used to sign sawtooth transactions, otherwise an ephemeral key will be generated") } fn wait_args(command: Command) -> Command { - command.arg( - Arg::new("wait") - .long("wait") - .num_args(0..=1) - .value_parser(clap::value_parser!(u64).range(0..)) - .default_value("5") - .default_missing_value("5") - .help("Wait for the specified number of blocks to be committed before exiting"), - ) + command.arg( + Arg::new("wait") + .long("wait") + .num_args(0..=1) + .value_parser(clap::value_parser!(u64).range(0..)) + .default_value("5") + .default_missing_value("5") + .help("Wait for the specified number of blocks to be committed before exiting"), + ) } fn bootstrap() -> Command { - wait_args( - Command::new("bootstrap") - .about("Initialize the OPA transaction processor with a root key from the keystore") - .arg(batcher_key()), - ) + wait_args( + Command::new("bootstrap") + .about("Initialize the OPA transaction processor with a root key from the keystore") + .arg(batcher_key()), + ) } fn generate() -> Command { - Command::new("generate") - .arg( - Arg::new("output") - .short('o') - .long("output") - .num_args(0..=1) - .help("The name to write the key to, if not specified then the key is written to stdout"), - ) - .about("Generate a new private key and write it to the keystore") + Command::new("generate") + .arg(Arg::new("output").short('o').long("output").num_args(0..=1).help( + "The name to write the key to, if not specified then the key is written to stdout", + )) + .about("Generate a new private key and write it to the keystore") } fn rotate_root() -> Command { - wait_args( - Command::new("rotate-root") - .about("Rotate the root key for the OPA transaction processor") - .arg( - Arg::new("new-root-key") - .short('n') - .long("new-root-key") - .env("NEW_ROOT_KEY") - .required(true) - .num_args(1) - .value_hint(ValueHint::FilePath) - .help("The name of the new key in the keystore to register as the root key"), - ) - .arg(batcher_key()), - ) + wait_args( + Command::new("rotate-root") + .about("Rotate the root key for the OPA transaction processor") + .arg( + Arg::new("new-root-key") + .short('n') + .long("new-root-key") + .env("NEW_ROOT_KEY") + .required(true) + .num_args(1) + .value_hint(ValueHint::FilePath) + .help("The name of the new key in the keystore to register as the root key"), + ) + .arg(batcher_key()), + ) } fn register_key() -> Command { - wait_args( - Command::new("register-key") - .about("Register a new non root key with the OPA transaction processor") - .arg( - Arg::new("new-key") - .long("new-key") - .required(true) - .num_args(1) - .value_hint(ValueHint::FilePath) - .help("The keystore name of a PEM-encoded key to register"), - ) - .arg( - Arg::new("id") - .short('i') - .long("id") - .required(true) - .num_args(1) - .value_hint(ValueHint::Unknown) - .value_parser(NonEmptyStringValueParser::new()) - .help("The id of the key"), - ) - .arg( - Arg::new("overwrite") - .short('o') - .long("overwrite") - .action(ArgAction::SetTrue) - .help("Replace any existing non-root key"), - ) - .arg(batcher_key()), - ) + wait_args( + Command::new("register-key") + .about("Register a new non root key with the OPA transaction processor") + .arg( + Arg::new("new-key") + .long("new-key") + .required(true) + .num_args(1) + .value_hint(ValueHint::FilePath) + .help("The keystore name of a PEM-encoded key to register"), + ) + .arg( + Arg::new("id") + .short('i') + .long("id") + .required(true) + .num_args(1) + .value_hint(ValueHint::Unknown) + .value_parser(NonEmptyStringValueParser::new()) + .help("The id of the key"), + ) + .arg( + Arg::new("overwrite") + .short('o') + .long("overwrite") + .action(ArgAction::SetTrue) + .help("Replace any existing non-root key"), + ) + .arg(batcher_key()), + ) } fn rotate_key() -> Command { - wait_args( - Command::new("rotate-key") - .about("Rotate the key with the specified id for the OPA transaction processor") - .arg( - Arg::new("current-key") - .long("current-key") - .env("CURRENT_KEY") - .required(true) - .num_args(1) - .value_hint(ValueHint::FilePath) - .help("The keystore name of the current registered key"), - ) - .arg( - Arg::new("new-key") - .long("new-key") - .env("NEW_KEY") - .required(true) - .num_args(1) - .value_hint(ValueHint::FilePath) - .help("The keystore name of the new key to register"), - ) - .arg( - Arg::new("id") - .short('i') - .long("id") - .required(true) - .num_args(1) - .value_hint(ValueHint::Unknown) - .value_parser(NonEmptyStringValueParser::new()) - .help("The id of the key"), - ) - .arg(batcher_key()), - ) + wait_args( + Command::new("rotate-key") + .about("Rotate the key with the specified id for the OPA transaction processor") + .arg( + Arg::new("current-key") + .long("current-key") + .env("CURRENT_KEY") + .required(true) + .num_args(1) + .value_hint(ValueHint::FilePath) + .help("The keystore name of the current registered key"), + ) + .arg( + Arg::new("new-key") + .long("new-key") + .env("NEW_KEY") + .required(true) + .num_args(1) + .value_hint(ValueHint::FilePath) + .help("The keystore name of the new key to register"), + ) + .arg( + Arg::new("id") + .short('i') + .long("id") + .required(true) + .num_args(1) + .value_hint(ValueHint::Unknown) + .value_parser(NonEmptyStringValueParser::new()) + .help("The id of the key"), + ) + .arg(batcher_key()), + ) } fn set_policy() -> Command { - wait_args( - Command::new("set-policy") - .about("Set policy with id, requires access to root private key") - .arg( - Arg::new("id") - .short('i') - .long("id") - .num_args(1) - .value_hint(ValueHint::Unknown) - .value_parser(NonEmptyStringValueParser::new()) - .default_value("default") - .help("The id of the new policy"), - ) - .arg( - Arg::new("policy") - .short('p') - .long("policy") - .num_args(1) - .required(true) - .value_hint(ValueHint::Url) - .value_parser(StringValueParser::new()) - .help("A path or url to a policy bundle"), - ) - .arg(batcher_key()), - ) + wait_args( + Command::new("set-policy") + .about("Set policy with id, requires access to root private key") + .arg( + Arg::new("id") + .short('i') + .long("id") + .num_args(1) + .value_hint(ValueHint::Unknown) + .value_parser(NonEmptyStringValueParser::new()) + .default_value("default") + .help("The id of the new policy"), + ) + .arg( + Arg::new("policy") + .short('p') + .long("policy") + .num_args(1) + .required(true) + .value_hint(ValueHint::Url) + .value_parser(StringValueParser::new()) + .help("A path or url to a policy bundle"), + ) + .arg(batcher_key()), + ) } fn get_key() -> Command { - Command::new("get-key") + Command::new("get-key") .about("Get the currently registered public key") .arg( Arg::new("id") @@ -192,206 +188,198 @@ fn get_key() -> Command { } fn get_policy() -> Command { - Command::new("get-policy") - .about("Get the currently registered policy") - .arg( - Arg::new("id") - .short('i') - .long("id") - .num_args(1) - .value_hint(ValueHint::Unknown) - .value_parser(NonEmptyStringValueParser::new()) - .default_value("default") - .help("The id of the policy, if not specified then the default policy is returned"), - ) - .arg( - Arg::new("output") - .short('o') - .long("output") - .num_args(1) - .required(true) - .value_hint(ValueHint::FilePath) - .value_parser(NonEmptyStringValueParser::new()) - .help("The path to write the policy to"), - ) + Command::new("get-policy") + .about("Get the currently registered policy") + .arg( + Arg::new("id") + .short('i') + .long("id") + .num_args(1) + .value_hint(ValueHint::Unknown) + .value_parser(NonEmptyStringValueParser::new()) + .default_value("default") + .help("The id of the policy, if not specified then the default policy is returned"), + ) + .arg( + Arg::new("output") + .short('o') + .long("output") + .num_args(1) + .required(true) + .value_hint(ValueHint::FilePath) + .value_parser(NonEmptyStringValueParser::new()) + .help("The path to write the policy to"), + ) } pub const LONG_VERSION: &str = const_format::formatcp!( - "{}:{}", - env!("CARGO_PKG_VERSION"), - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../.VERSION")) + "{}:{}", + env!("CARGO_PKG_VERSION"), + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../.VERSION")) ); pub fn cli() -> Command { - info!(opa_version = LONG_VERSION); - Command::new("opactl") - .version(LONG_VERSION) - .author("Blockchain Technology Partners") - .about("A command line tool for interacting with the OPA transaction processor") - .arg( - Arg::new("keystore-path") - .long("keystore-path") - .help("The path to a directory containing keys") - .value_parser(clap::value_parser!(PathBuf)) - .value_hint(ValueHint::DirPath) - .env("KEYSTORE_PATH") - .default_value("."), - ) - .arg( - Arg::new("batcher-key-from-path") - .long("batcher-key-from-path") - .action(ArgAction::SetTrue) - .help("Load batcher key from keystore path") - .conflicts_with("batcher-key-from-vault") - .conflicts_with("batcher-key-generated"), - ) - .arg( - Arg::new("batcher-key-from-vault") - .long("batcher-key-from-vault") - .action(ArgAction::SetTrue) - .help("Use Hashicorp Vault to store the batcher key") - .conflicts_with("batcher-key-from-path") - .conflicts_with("batcher-key-generated"), - ) - .arg( - Arg::new("batcher-key-generated") - .long("batcher-key-generated") - .action(ArgAction::SetTrue) - .help("Generate the batcher key in memory") - .conflicts_with("batcher-key-from-path") - .conflicts_with("batcher-key-from-vault"), - ) - .arg( - Arg::new("opa-key-from-path") - .long("opa-key-from-path") - .action(ArgAction::SetTrue) - .help("Use keystore path for the opa key located in 'opa-pk'") - .conflicts_with("opa-key-from-vault"), - ) - .arg( - Arg::new("opa-key-from-vault") - .long("opa-key-from-vault") - .action(ArgAction::SetTrue) - .help("Use Hashicorp Vault to store the Opa key") - .conflicts_with("opa-key-from-path"), - ) - .arg( - Arg::new("vault-address") - .long("vault-address") - .num_args(0..=1) - .value_parser(clap::value_parser!(Url)) - .value_hint(ValueHint::Url) - .help("URL for connecting to Hashicorp Vault") - .env("VAULT_ADDRESS"), - ) - .arg( - Arg::new("vault-token") - .long("vault-token") - .num_args(0..=1) - .help("Token for connecting to Hashicorp Vault") - .env("VAULT_TOKEN"), - ) - .arg( - Arg::new("vault-mount-path") - .long("vault-mount-path") - .num_args(0..=1) - .value_hint(ValueHint::DirPath) - .help("Mount path for vault secrets") - .default_value("/") - .env("VAULT_MOUNT_PATH"), - ) - .arg( - Arg::new("sawtooth-address") - .short('a') - .long("sawtooth-address") - .num_args(0..=1) - .help("The address of the Sawtooth ZMQ api, as zmq://host:port") - .value_parser(clap::value_parser!(Url)) - .env("SAWTOOTH_ADDRESS") - .default_value("tcp://localhost:4004"), - ) - .subcommand(bootstrap()) - .subcommand(generate()) - .subcommand(rotate_root()) - .subcommand(register_key()) - .subcommand(rotate_key()) - .subcommand(set_policy()) - .subcommand(get_key()) - .subcommand(get_policy()) + info!(opa_version = LONG_VERSION); + Command::new("opactl") + .version(LONG_VERSION) + .author("Blockchain Technology Partners") + .about("A command line tool for interacting with the OPA transaction processor") + .arg( + Arg::new("keystore-path") + .long("keystore-path") + .help("The path to a directory containing keys") + .value_parser(clap::value_parser!(PathBuf)) + .value_hint(ValueHint::DirPath) + .env("KEYSTORE_PATH") + .default_value("."), + ) + .arg( + Arg::new("batcher-key-from-path") + .long("batcher-key-from-path") + .action(ArgAction::SetTrue) + .help("Load batcher key from keystore path") + .conflicts_with("batcher-key-from-vault") + .conflicts_with("batcher-key-generated"), + ) + .arg( + Arg::new("batcher-key-from-vault") + .long("batcher-key-from-vault") + .action(ArgAction::SetTrue) + .help("Use Hashicorp Vault to store the batcher key") + .conflicts_with("batcher-key-from-path") + .conflicts_with("batcher-key-generated"), + ) + .arg( + Arg::new("batcher-key-generated") + .long("batcher-key-generated") + .action(ArgAction::SetTrue) + .help("Generate the batcher key in memory") + .conflicts_with("batcher-key-from-path") + .conflicts_with("batcher-key-from-vault"), + ) + .arg( + Arg::new("opa-key-from-path") + .long("opa-key-from-path") + .action(ArgAction::SetTrue) + .help("Use keystore path for the opa key located in 'opa-pk'") + .conflicts_with("opa-key-from-vault"), + ) + .arg( + Arg::new("opa-key-from-vault") + .long("opa-key-from-vault") + .action(ArgAction::SetTrue) + .help("Use Hashicorp Vault to store the Opa key") + .conflicts_with("opa-key-from-path"), + ) + .arg( + Arg::new("vault-address") + .long("vault-address") + .num_args(0..=1) + .value_parser(clap::value_parser!(Url)) + .value_hint(ValueHint::Url) + .help("URL for connecting to Hashicorp Vault") + .env("VAULT_ADDRESS"), + ) + .arg( + Arg::new("vault-token") + .long("vault-token") + .num_args(0..=1) + .help("Token for connecting to Hashicorp Vault") + .env("VAULT_TOKEN"), + ) + .arg( + Arg::new("vault-mount-path") + .long("vault-mount-path") + .num_args(0..=1) + .value_hint(ValueHint::DirPath) + .help("Mount path for vault secrets") + .default_value("/") + .env("VAULT_MOUNT_PATH"), + ) + .arg( + Arg::new("sawtooth-address") + .short('a') + .long("sawtooth-address") + .num_args(0..=1) + .help("The address of the Sawtooth ZMQ api, as zmq://host:port") + .value_parser(clap::value_parser!(Url)) + .env("SAWTOOTH_ADDRESS") + .default_value("tcp://localhost:4004"), + ) + .subcommand(bootstrap()) + .subcommand(generate()) + .subcommand(rotate_root()) + .subcommand(register_key()) + .subcommand(rotate_key()) + .subcommand(set_policy()) + .subcommand(get_key()) + .subcommand(get_policy()) } // Chronicle secret store needs to know what secret names are used in advance, // so extract from potential cli args fn additional_secret_names(expected: Vec<&str>, matches: &ArgMatches) -> Vec { - expected - .iter() - .filter_map(|x| matches.get_one::(x).cloned()) - .collect() + expected.iter().filter_map(|x| matches.get_one::(x).cloned()).collect() } -// Batcher keys may be ephemeral if batcher-key-from-path is not set, also we need to know secret names in advance, so must inspect the supplied CLI arguments +// Batcher keys may be ephemeral if batcher-key-from-path is not set, also we need to know secret +// names in advance, so must inspect the supplied CLI arguments pub(crate) async fn configure_signing( - expected: Vec<&str>, - root_matches: &ArgMatches, - matches: &ArgMatches, + expected: Vec<&str>, + root_matches: &ArgMatches, + matches: &ArgMatches, ) -> Result { - let mut secret_names = opa_secret_names(); - secret_names.append( - &mut additional_secret_names(expected, matches) - .into_iter() - .map(|name| (OPA_NAMESPACE.to_string(), name.to_string())) - .collect(), - ); - let keystore_path = root_matches.get_one::("keystore-path").unwrap(); + let mut secret_names = opa_secret_names(); + secret_names.append( + &mut additional_secret_names(expected, matches) + .into_iter() + .map(|name| (OPA_NAMESPACE.to_string(), name.to_string())) + .collect(), + ); + let keystore_path = root_matches.get_one::("keystore-path").unwrap(); - let opa_key_from_vault = root_matches - .get_one("opa-key-from-vault") - .is_some_and(|x| *x); - let opa_secret_options = if opa_key_from_vault { - ChronicleSecretsOptions::stored_in_vault( - matches.get_one("vault-url").unwrap(), - matches.get_one("vault-token").cloned().unwrap(), - matches.get_one("vault-mount-path").cloned().unwrap(), - ) - } else { - ChronicleSecretsOptions::stored_at_path(keystore_path) - }; - let opa_secret = (OPA_NAMESPACE.to_string(), opa_secret_options); + let opa_key_from_vault = root_matches.get_one("opa-key-from-vault").is_some_and(|x| *x); + let opa_secret_options = if opa_key_from_vault { + ChronicleSecretsOptions::stored_in_vault( + matches.get_one("vault-url").unwrap(), + matches.get_one("vault-token").cloned().unwrap(), + matches.get_one("vault-mount-path").cloned().unwrap(), + ) + } else { + ChronicleSecretsOptions::stored_at_path(keystore_path) + }; + let opa_secret = (OPA_NAMESPACE.to_string(), opa_secret_options); - let batcher_key_from_path = root_matches - .get_one("batcher-key-from-path") - .is_some_and(|x| *x); - let batcher_key_from_vault = root_matches - .get_one("batcher-key-from-vault") - .is_some_and(|x| *x); - let batcher_secret_options = if batcher_key_from_path { - ChronicleSecretsOptions::stored_at_path(keystore_path) - } else if batcher_key_from_vault { - ChronicleSecretsOptions::stored_in_vault( - matches.get_one("vault-url").unwrap(), - matches.get_one("vault-token").cloned().unwrap(), - matches.get_one("vault-mount-path").cloned().unwrap(), - ) - } else { - ChronicleSecretsOptions::generate_in_memory() - }; - let batcher_secret = (BATCHER_NAMESPACE.to_string(), batcher_secret_options); + let batcher_key_from_path = root_matches.get_one("batcher-key-from-path").is_some_and(|x| *x); + let batcher_key_from_vault = root_matches.get_one("batcher-key-from-vault").is_some_and(|x| *x); + let batcher_secret_options = if batcher_key_from_path { + ChronicleSecretsOptions::stored_at_path(keystore_path) + } else if batcher_key_from_vault { + ChronicleSecretsOptions::stored_in_vault( + matches.get_one("vault-url").unwrap(), + matches.get_one("vault-token").cloned().unwrap(), + matches.get_one("vault-mount-path").cloned().unwrap(), + ) + } else { + ChronicleSecretsOptions::generate_in_memory() + }; + let batcher_secret = (BATCHER_NAMESPACE.to_string(), batcher_secret_options); - let secrets = vec![opa_secret, batcher_secret]; - ChronicleSigning::new(secret_names, secrets).await + let secrets = vec![opa_secret, batcher_secret]; + ChronicleSigning::new(secret_names, secrets).await } #[derive(Debug, Clone, Copy)] pub(crate) enum Wait { - NoWait, - NumberOfBlocks(u64), + NoWait, + NumberOfBlocks(u64), } impl Wait { - pub(crate) fn from_matches(matches: &ArgMatches) -> Self { - match matches.get_one::("wait") { - Some(blocks) if *blocks > 0 => Wait::NumberOfBlocks(*blocks), - _ => Wait::NoWait, - } - } + pub(crate) fn from_matches(matches: &ArgMatches) -> Self { + match matches.get_one::("wait") { + Some(blocks) if *blocks > 0 => Wait::NumberOfBlocks(*blocks), + _ => Wait::NoWait, + } + } } diff --git a/crates/opactl/src/main.rs b/crates/opactl/src/main.rs index 25a38b456..602f8115e 100644 --- a/crates/opactl/src/main.rs +++ b/crates/opactl/src/main.rs @@ -1,6 +1,6 @@ use async_stl_client::{ - sawtooth::MessageBuilder, - zmq_client::{HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel}, + sawtooth::MessageBuilder, + zmq_client::{HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel}, }; use chronicle_signing::{OpaKnownKeyNamesSigner, SecretError, OPA_PK}; use clap::ArgMatches; @@ -8,19 +8,19 @@ use cli::{configure_signing, Wait}; use common::import::{load_bytes_from_url, FromUrlError}; use futures::{channel::oneshot, Future, FutureExt, StreamExt}; use k256::{ - pkcs8::{EncodePrivateKey, LineEnding}, - SecretKey, + pkcs8::{EncodePrivateKey, LineEnding}, + SecretKey, }; use opa_tp_protocol::{ - address::{FAMILY, VERSION}, - async_stl_client::{ - error::SawtoothCommunicationError, - ledger::{LedgerReader, LedgerWriter, TransactionId}, - }, - state::{key_address, policy_address, Keys, OpaOperationEvent}, - submission::SubmissionBuilder, - transaction::OpaSubmitTransaction, - OpaLedger, + address::{FAMILY, VERSION}, + async_stl_client::{ + error::SawtoothCommunicationError, + ledger::{LedgerReader, LedgerWriter, TransactionId}, + }, + state::{key_address, policy_address, Keys, OpaOperationEvent}, + submission::SubmissionBuilder, + transaction::OpaSubmitTransaction, + OpaLedger, }; use serde::Deserialize; use serde_derive::Serialize; @@ -37,119 +37,119 @@ mod cli; #[derive(Error, Debug)] pub enum OpaCtlError { - #[error("Operation cancelled {0}")] - Cancelled(oneshot::Canceled), + #[error("Operation cancelled {0}")] + Cancelled(oneshot::Canceled), - #[error("Communication error: {0}")] - Communication(#[from] SawtoothCommunicationError), + #[error("Communication error: {0}")] + Communication(#[from] SawtoothCommunicationError), - #[error("IO error: {0}")] - IO(#[from] std::io::Error), + #[error("IO error: {0}")] + IO(#[from] std::io::Error), - #[error("Json error: {0}")] - Json(#[from] serde_json::Error), + #[error("Json error: {0}")] + Json(#[from] serde_json::Error), - #[error("Pkcs8 error")] - Pkcs8, + #[error("Pkcs8 error")] + Pkcs8, - #[error("Transaction failed: {0}")] - TransactionFailed(String), + #[error("Transaction failed: {0}")] + TransactionFailed(String), - #[error("Transaction not found after wait: {0}")] - TransactionNotFound(TransactionId), + #[error("Transaction not found after wait: {0}")] + TransactionNotFound(TransactionId), - #[error("Error loading from URL: {0}")] - Url(#[from] FromUrlError), + #[error("Error loading from URL: {0}")] + Url(#[from] FromUrlError), - #[error("Utf8 error: {0}")] - Utf8(#[from] std::str::Utf8Error), + #[error("Utf8 error: {0}")] + Utf8(#[from] std::str::Utf8Error), - #[error("Signing: {0}")] - Signing(#[from] SecretError), + #[error("Signing: {0}")] + Signing(#[from] SecretError), - #[error("Missing Argument")] - MissingArgument(String), + #[error("Missing Argument")] + MissingArgument(String), } impl UFE for OpaCtlError {} #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Waited { - NoWait, - WaitedAndFound(OpaOperationEvent), - WaitedAndOperationFailed(OpaOperationEvent), - WaitedAndDidNotFind, + NoWait, + WaitedAndFound(OpaOperationEvent), + WaitedAndOperationFailed(OpaOperationEvent), + WaitedAndDidNotFind, } pub struct RetryLedgerWriter { - inner: W, + inner: W, } impl< - W: LedgerWriter - + Send - + Sync, - > RetryLedgerWriter + W: LedgerWriter + + Send + + Sync, + > RetryLedgerWriter { - pub fn new(inner: W) -> Self { - Self { inner } - } + pub fn new(inner: W) -> Self { + Self { inner } + } } #[async_trait::async_trait] impl< - W: LedgerWriter - + Send - + Sync, - > LedgerWriter for RetryLedgerWriter + W: LedgerWriter + + Send + + Sync, + > LedgerWriter for RetryLedgerWriter { - type Error = W::Error; - type Transaction = W::Transaction; - - async fn submit( - &self, - tx: &Self::Transaction, - ) -> Result, Self::Error)> { - tokio::time::sleep(Duration::from_secs(1)).await; - loop { - match self.inner.submit(tx).await { - Err((_id, SawtoothCommunicationError::NoConnectedValidators)) => { - debug!("Awaiting validator connection, retrying"); - tokio::time::sleep(Duration::from_secs(1)).await; - } - other => return other, - } - } - } - - fn message_builder(&self) -> &MessageBuilder { - self.inner.message_builder() - } + type Error = W::Error; + type Transaction = W::Transaction; + + async fn submit( + &self, + tx: &Self::Transaction, + ) -> Result, Self::Error)> { + tokio::time::sleep(Duration::from_secs(1)).await; + loop { + match self.inner.submit(tx).await { + Err((_id, SawtoothCommunicationError::NoConnectedValidators)) => { + debug!("Awaiting validator connection, retrying"); + tokio::time::sleep(Duration::from_secs(1)).await; + }, + other => return other, + } + } + } + + fn message_builder(&self) -> &MessageBuilder { + self.inner.message_builder() + } } /// Collect incoming transaction ids before running submission, as there is the /// potential to miss transactions if we do not collect them 'before' submission async fn ambient_transactions< - R: LedgerReader - + Send - + Sync - + Clone - + 'static, + R: LedgerReader + + Send + + Sync + + Clone + + 'static, >( - reader: R, - goal_tx_id: TransactionId, - max_steps: u64, + reader: R, + goal_tx_id: TransactionId, + max_steps: u64, ) -> impl Future> { - let span = span!(Level::DEBUG, "wait_for_opa_transaction"); + let span = span!(Level::DEBUG, "wait_for_opa_transaction"); - // Set up a oneshot channel to notify the returned task - let (notify_tx, notify_rx) = oneshot::channel::(); + // Set up a oneshot channel to notify the returned task + let (notify_tx, notify_rx) = oneshot::channel::(); - // And a oneshot channel to ensure we are receiving events from the chain - // before we return - let (receiving_events_tx, receiving_events_rx) = oneshot::channel::<()>(); + // And a oneshot channel to ensure we are receiving events from the chain + // before we return + let (receiving_events_tx, receiving_events_rx) = oneshot::channel::<()>(); - Handle::current().spawn(async move { + Handle::current().spawn(async move { // We can immediately return if we are not waiting debug!(waiting_for=?goal_tx_id, max_steps=?max_steps); let goal_clone = goal_tx_id.clone(); @@ -203,746 +203,707 @@ async fn ambient_transactions< } }.instrument(span)); - // Wait for the task to start receiving events - let _ = receiving_events_rx.await; + // Wait for the task to start receiving events + let _ = receiving_events_rx.await; - notify_rx + notify_rx } #[instrument(skip(reader, writer, matches, submission))] async fn handle_wait< - R: LedgerReader - + Clone - + Send - + Sync - + 'static, - W: LedgerWriter - + Send - + Sync, + R: LedgerReader + + Clone + + Send + + Sync + + 'static, + W: LedgerWriter + + Send + + Sync, >( - matches: &ArgMatches, - reader: R, - writer: W, - submission: OpaSubmitTransaction, + matches: &ArgMatches, + reader: R, + writer: W, + submission: OpaSubmitTransaction, ) -> Result<(Waited, R), OpaCtlError> { - let wait = Wait::from_matches(matches); - let writer = RetryLedgerWriter::new(writer); - match wait { - Wait::NoWait => { - writer.submit(&submission).await.map_err(|(_id, e)| e)?; - - Ok((Waited::NoWait, reader)) - } - Wait::NumberOfBlocks(blocks) => { - let tx_id = writer.submit(&submission).await.map_err(|(_id, e)| e)?; - let waiter = ambient_transactions(reader.clone(), tx_id.clone(), blocks).await; - debug!(awaiting_tx=%tx_id, waiting_blocks=%blocks); - match waiter.await { - Ok(Waited::WaitedAndDidNotFind) => Err(OpaCtlError::TransactionNotFound(tx_id)), - Ok(Waited::WaitedAndOperationFailed(OpaOperationEvent::Error(e))) => { - Err(OpaCtlError::TransactionFailed(e)) - } - Ok(x) => Ok((x, reader)), - Err(e) => Err(OpaCtlError::Cancelled(e)), - } - } - } + let wait = Wait::from_matches(matches); + let writer = RetryLedgerWriter::new(writer); + match wait { + Wait::NoWait => { + writer.submit(&submission).await.map_err(|(_id, e)| e)?; + + Ok((Waited::NoWait, reader)) + }, + Wait::NumberOfBlocks(blocks) => { + let tx_id = writer.submit(&submission).await.map_err(|(_id, e)| e)?; + let waiter = ambient_transactions(reader.clone(), tx_id.clone(), blocks).await; + debug!(awaiting_tx=%tx_id, waiting_blocks=%blocks); + match waiter.await { + Ok(Waited::WaitedAndDidNotFind) => Err(OpaCtlError::TransactionNotFound(tx_id)), + Ok(Waited::WaitedAndOperationFailed(OpaOperationEvent::Error(e))) => + Err(OpaCtlError::TransactionFailed(e)), + Ok(x) => Ok((x, reader)), + Err(e) => Err(OpaCtlError::Cancelled(e)), + } + }, + } } async fn dispatch_args< - W: LedgerWriter - + Send - + Sync, - R: LedgerReader - + Send - + Sync - + Clone - + 'static, + W: LedgerWriter + + Send + + Sync, + R: LedgerReader + + Send + + Sync + + Clone + + 'static, >( - matches: ArgMatches, - writer: W, - reader: R, + matches: ArgMatches, + writer: W, + reader: R, ) -> Result<(Waited, R), OpaCtlError> { - let span = span!(Level::TRACE, "dispatch_args"); - let _entered = span.enter(); - let span_id = span.id().map(|x| x.into_u64()).unwrap_or(u64::MAX); - match matches.subcommand() { - Some(("bootstrap", command_matches)) => { - let signing = configure_signing(vec![], &matches, command_matches).await?; - let bootstrap = - SubmissionBuilder::bootstrap_root(signing.opa_verifying().await?).build(span_id); - Ok(handle_wait( - command_matches, - reader, - writer, - OpaSubmitTransaction::bootstrap_root(bootstrap, &signing), - ) - .await?) - } - Some(("generate", matches)) => { - let key = SecretKey::random(StdRng::from_entropy()); - let key = key - .to_pkcs8_pem(LineEnding::CRLF) - .map_err(|_| OpaCtlError::Pkcs8)?; - - if let Some(path) = matches.get_one::("output") { - let mut file = File::create(path)?; - file.write_all(key.as_bytes())?; - } else { - print!("{}", *key); - } - - Ok((Waited::NoWait, reader)) - } - Some(("rotate-root", command_matches)) => { - let signing = - configure_signing(vec!["new-root-key"], &matches, command_matches).await?; - let rotate_key = SubmissionBuilder::rotate_key( - "root", - &signing, - OPA_PK, - command_matches - .get_one::("new-root-key") - .ok_or_else(|| OpaCtlError::MissingArgument("new-root-key".to_owned()))?, - ) - .await? - .build(span_id); - Ok(handle_wait( - command_matches, - reader, - writer, - OpaSubmitTransaction::rotate_root(rotate_key, &signing), - ) - .await?) - } - Some(("register-key", command_matches)) => { - let signing = configure_signing(vec!["new-key"], &matches, command_matches).await?; - let new_key = &command_matches - .get_one::("new-key") - .ok_or_else(|| OpaCtlError::MissingArgument("new-key".to_owned()))?; - let id = command_matches.get_one::("id").unwrap(); - let overwrite_existing = command_matches.get_flag("overwrite"); - let register_key = - SubmissionBuilder::register_key(id, new_key, &signing, overwrite_existing) - .await? - .build(span_id); - Ok(handle_wait( - command_matches, - reader, - writer, - OpaSubmitTransaction::register_key(id, register_key, &signing, overwrite_existing), - ) - .await?) - } - Some(("rotate-key", command_matches)) => { - let signing = - configure_signing(vec!["current-key", "new-key"], &matches, command_matches) - .await?; - - let current_key = &command_matches - .get_one::("current-key") - .ok_or_else(|| OpaCtlError::MissingArgument("new-key".to_owned()))?; - let new_key = &command_matches - .get_one::("new-key") - .ok_or_else(|| OpaCtlError::MissingArgument("new-key".to_owned()))?; - let id = command_matches.get_one::("id").unwrap(); - let rotate_key = SubmissionBuilder::rotate_key(id, &signing, new_key, current_key) - .await? - .build(span_id); - Ok(handle_wait( - command_matches, - reader, - writer, - OpaSubmitTransaction::rotate_key(id, rotate_key, &signing), - ) - .await?) - } - Some(("set-policy", command_matches)) => { - let signing = configure_signing(vec![], &matches, command_matches).await?; - let policy: &String = command_matches.get_one("policy").unwrap(); - - let policy = load_bytes_from_url(policy).await?; - - let id = command_matches.get_one::("id").unwrap(); - - let bootstrap = SubmissionBuilder::set_policy(id, policy, &signing) - .await? - .build(span_id); - Ok(handle_wait( - command_matches, - reader, - writer, - OpaSubmitTransaction::set_policy(id, bootstrap, &signing), - ) - .await?) - } - Some(("get-key", matches)) => { - let key: Vec = reader - .get_state_entry(&key_address(matches.get_one::("id").unwrap())) - .await?; - - debug!(loaded_key = ?from_utf8(&key)); - - let key: Keys = serde_json::from_slice(&key)?; - - let key = key.current.key; - - if let Some(path) = matches.get_one::("output") { - let mut file = File::create(path)?; - file.write_all(key.as_bytes())?; - } else { - print!("{key}"); - } - - Ok((Waited::NoWait, reader)) - } - Some(("get-policy", matches)) => { - let policy: Result, _> = reader - .get_state_entry(&policy_address(matches.get_one::("id").unwrap())) - .await; - - if let Err(SawtoothCommunicationError::ResourceNotFound) = policy { - print!("No policy found"); - return Ok((Waited::NoWait, reader)); - } - - let policy = policy?; - - if let Some(path) = matches.get_one::("output") { - let mut file = File::create(path)?; - file.write_all(&policy)?; - } - - Ok((Waited::NoWait, reader)) - } - _ => Ok((Waited::NoWait, reader)), - } + let span = span!(Level::TRACE, "dispatch_args"); + let _entered = span.enter(); + let span_id = span.id().map(|x| x.into_u64()).unwrap_or(u64::MAX); + match matches.subcommand() { + Some(("bootstrap", command_matches)) => { + let signing = configure_signing(vec![], &matches, command_matches).await?; + let bootstrap = + SubmissionBuilder::bootstrap_root(signing.opa_verifying().await?).build(span_id); + Ok(handle_wait( + command_matches, + reader, + writer, + OpaSubmitTransaction::bootstrap_root(bootstrap, &signing), + ) + .await?) + }, + Some(("generate", matches)) => { + let key = SecretKey::random(StdRng::from_entropy()); + let key = key.to_pkcs8_pem(LineEnding::CRLF).map_err(|_| OpaCtlError::Pkcs8)?; + + if let Some(path) = matches.get_one::("output") { + let mut file = File::create(path)?; + file.write_all(key.as_bytes())?; + } else { + print!("{}", *key); + } + + Ok((Waited::NoWait, reader)) + }, + Some(("rotate-root", command_matches)) => { + let signing = + configure_signing(vec!["new-root-key"], &matches, command_matches).await?; + let rotate_key = SubmissionBuilder::rotate_key( + "root", + &signing, + OPA_PK, + command_matches + .get_one::("new-root-key") + .ok_or_else(|| OpaCtlError::MissingArgument("new-root-key".to_owned()))?, + ) + .await? + .build(span_id); + Ok(handle_wait( + command_matches, + reader, + writer, + OpaSubmitTransaction::rotate_root(rotate_key, &signing), + ) + .await?) + }, + Some(("register-key", command_matches)) => { + let signing = configure_signing(vec!["new-key"], &matches, command_matches).await?; + let new_key = &command_matches + .get_one::("new-key") + .ok_or_else(|| OpaCtlError::MissingArgument("new-key".to_owned()))?; + let id = command_matches.get_one::("id").unwrap(); + let overwrite_existing = command_matches.get_flag("overwrite"); + let register_key = + SubmissionBuilder::register_key(id, new_key, &signing, overwrite_existing) + .await? + .build(span_id); + Ok(handle_wait( + command_matches, + reader, + writer, + OpaSubmitTransaction::register_key(id, register_key, &signing, overwrite_existing), + ) + .await?) + }, + Some(("rotate-key", command_matches)) => { + let signing = + configure_signing(vec!["current-key", "new-key"], &matches, command_matches) + .await?; + + let current_key = &command_matches + .get_one::("current-key") + .ok_or_else(|| OpaCtlError::MissingArgument("new-key".to_owned()))?; + let new_key = &command_matches + .get_one::("new-key") + .ok_or_else(|| OpaCtlError::MissingArgument("new-key".to_owned()))?; + let id = command_matches.get_one::("id").unwrap(); + let rotate_key = SubmissionBuilder::rotate_key(id, &signing, new_key, current_key) + .await? + .build(span_id); + Ok(handle_wait( + command_matches, + reader, + writer, + OpaSubmitTransaction::rotate_key(id, rotate_key, &signing), + ) + .await?) + }, + Some(("set-policy", command_matches)) => { + let signing = configure_signing(vec![], &matches, command_matches).await?; + let policy: &String = command_matches.get_one("policy").unwrap(); + + let policy = load_bytes_from_url(policy).await?; + + let id = command_matches.get_one::("id").unwrap(); + + let bootstrap = + SubmissionBuilder::set_policy(id, policy, &signing).await?.build(span_id); + Ok(handle_wait( + command_matches, + reader, + writer, + OpaSubmitTransaction::set_policy(id, bootstrap, &signing), + ) + .await?) + }, + Some(("get-key", matches)) => { + let key: Vec = reader + .get_state_entry(&key_address(matches.get_one::("id").unwrap())) + .await?; + + debug!(loaded_key = ?from_utf8(&key)); + + let key: Keys = serde_json::from_slice(&key)?; + + let key = key.current.key; + + if let Some(path) = matches.get_one::("output") { + let mut file = File::create(path)?; + file.write_all(key.as_bytes())?; + } else { + print!("{key}"); + } + + Ok((Waited::NoWait, reader)) + }, + Some(("get-policy", matches)) => { + let policy: Result, _> = reader + .get_state_entry(&policy_address(matches.get_one::("id").unwrap())) + .await; + + if let Err(SawtoothCommunicationError::ResourceNotFound) = policy { + print!("No policy found"); + return Ok((Waited::NoWait, reader)) + } + + let policy = policy?; + + if let Some(path) = matches.get_one::("output") { + let mut file = File::create(path)?; + file.write_all(&policy)?; + } + + Ok((Waited::NoWait, reader)) + }, + _ => Ok((Waited::NoWait, reader)), + } } #[tokio::main] async fn main() { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - let args = cli::cli().get_matches(); - let address: &Url = args.get_one("sawtooth-address").unwrap(); - let client = ZmqRequestResponseSawtoothChannel::new( - &format!("opactl-{}", uuid::Uuid::new_v4()), - &[format!( - "{}:{}", - address.host().expect("host").to_owned(), - address.port().unwrap_or(4004) - ) - .to_socket_addrs() - .unwrap() - .next() - .unwrap()], - HighestBlockValidatorSelector, - ) - .unwrap() - .retrying(); - - let reader = OpaLedger::new(client, FAMILY, VERSION); - let writer = reader.clone(); - - dispatch_args(args, writer, reader) - .await - .map_err(|opactl| { - error!(?opactl); - opactl.into_ufe().print(); - std::process::exit(1); - }) - .map(|(waited, _reader)| { - if let Waited::WaitedAndFound(op) = waited { - println!( - "{}", - serde_json::to_string_pretty(&serde_json::to_value(op).unwrap()).unwrap() - ); - } - }) - .ok(); + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + let args = cli::cli().get_matches(); + let address: &Url = args.get_one("sawtooth-address").unwrap(); + let client = ZmqRequestResponseSawtoothChannel::new( + &format!("opactl-{}", uuid::Uuid::new_v4()), + &[format!( + "{}:{}", + address.host().expect("host").to_owned(), + address.port().unwrap_or(4004) + ) + .to_socket_addrs() + .unwrap() + .next() + .unwrap()], + HighestBlockValidatorSelector, + ) + .unwrap() + .retrying(); + + let reader = OpaLedger::new(client, FAMILY, VERSION); + let writer = reader.clone(); + + dispatch_args(args, writer, reader) + .await + .map_err(|opactl| { + error!(?opactl); + opactl.into_ufe().print(); + std::process::exit(1); + }) + .map(|(waited, _reader)| { + if let Waited::WaitedAndFound(op) = waited { + println!( + "{}", + serde_json::to_string_pretty(&serde_json::to_value(op).unwrap()).unwrap() + ); + } + }) + .ok(); } // Use as much of the opa-tp as possible, by using a simulated `RequestResponseSawtoothChannel` #[cfg(test)] pub mod test { - use async_stl_client::{ - error::SawtoothCommunicationError, - ledger::SawtoothLedger, - messages::{ - message::MessageType, BlockHeader, ClientBatchSubmitResponse, - ClientBlockGetByNumRequest, ClientBlockGetResponse, ClientBlockListResponse, - ClientEventsSubscribeResponse, ClientStateGetRequest, ClientStateGetResponse, - }, - zmq_client::{HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel}, - }; - use clap::ArgMatches; - use futures::{select, FutureExt, SinkExt, StreamExt}; - use opa_tp_protocol::{ - address::{FAMILY, VERSION}, - messages::OpaEvent, - state::OpaOperationEvent, - transaction::OpaSubmitTransaction, - }; - - use k256::{ - pkcs8::{EncodePrivateKey, LineEnding}, - SecretKey, - }; - use opa_tp::{abstract_tp::TP, tp::OpaTransactionHandler}; - - use prost::Message; - use rand::rngs::StdRng; - use rand_core::SeedableRng; - use sawtooth_sdk::{ - messages::client_batch_submit::ClientBatchSubmitRequest, - processor::handler::{ContextError, TransactionContext}, - }; - use serde_json::{self, Value}; - use tmq::{router, Context, Multipart}; - use tokio::runtime; - use uuid::Uuid; - - use std::{ - cell::RefCell, - collections::BTreeMap, - io::Write, - net::{Ipv4Addr, SocketAddr}, - sync::{Arc, Mutex}, - thread, - time::Duration, - }; - use tempfile::{NamedTempFile, TempDir}; - use tokio_stream::wrappers::UnboundedReceiverStream; - use tracing::{debug, error, info, instrument}; - - use crate::{cli, dispatch_args}; - - type TestTxEvents = Vec<(String, Vec<(String, String)>, Vec)>; - - #[async_trait::async_trait] - pub trait SimulatedSawtoothBehavior { - async fn handle_request( - &self, - message_type: MessageType, - request: Vec, - ) -> Result<(MessageType, Vec), SawtoothCommunicationError>; - } - - pub type OpaLedger = - SawtoothLedger; - - type PrintableEvent = Vec<(String, Vec<(String, String)>, Value)>; - - #[derive(Clone)] - pub struct TestTransactionContext { - pub state: RefCell>>, - pub events: RefCell, - tx: tokio::sync::mpsc::UnboundedSender)>>, - } - - impl TestTransactionContext { - pub fn new(tx: tokio::sync::mpsc::UnboundedSender)>>) -> Self { - Self { - state: RefCell::new(BTreeMap::new()), - events: RefCell::new(vec![]), - tx, - } - } - - pub fn new_with_state( - tx: tokio::sync::mpsc::UnboundedSender)>>, - state: BTreeMap>, - ) -> Self { - Self { - state: state.into(), - events: RefCell::new(vec![]), - tx, - } - } - - pub fn readable_state(&self) -> Vec<(String, Value)> { - // Deal with the fact that policies are raw bytes, but meta data and - // keys are json - self.state - .borrow() - .iter() - .map(|(k, v)| { - let as_string = String::from_utf8(v.clone()).unwrap(); - if serde_json::from_str::(&as_string).is_ok() { - (k.clone(), serde_json::from_str(&as_string).unwrap()) - } else { - (k.clone(), serde_json::to_value(v.clone()).unwrap()) - } - }) - .collect() - } - - pub fn readable_events(&self) -> PrintableEvent { - self.events - .borrow() - .iter() - .map(|(k, attr, data)| { - ( - k.clone(), - attr.clone(), - match &::decode(&**data) - .unwrap() - .payload - .unwrap() - { - opa_tp_protocol::messages::opa_event::Payload::Operation(operation) => { - serde_json::from_str(operation).unwrap() - } - opa_tp_protocol::messages::opa_event::Payload::Error(error) => { - serde_json::from_str(error).unwrap() - } - }, - ) - }) - .collect() - } - } - - impl sawtooth_sdk::processor::handler::TransactionContext for TestTransactionContext { - fn add_receipt_data( - self: &TestTransactionContext, - _data: &[u8], - ) -> Result<(), ContextError> { - unimplemented!() - } - - #[instrument(skip(self))] - fn add_event( - self: &TestTransactionContext, - event_type: String, - attributes: Vec<(String, String)>, - data: &[u8], - ) -> Result<(), ContextError> { - let stl_event = async_stl_client::messages::Event { - event_type: event_type.clone(), - attributes: attributes - .iter() - .map(|(k, v)| async_stl_client::messages::event::Attribute { - key: k.clone(), - value: v.clone(), - }) - .collect(), - data: data.to_vec(), - }; - - let list = async_stl_client::messages::EventList { - events: vec![stl_event], - }; - let stl_event: Vec = list.encode_to_vec(); - - self.tx - .send(Some((MessageType::ClientEvents, stl_event))) - .unwrap(); - - self.events - .borrow_mut() - .push((event_type, attributes, data.to_vec())); - - Ok(()) - } - - fn delete_state_entries( - self: &TestTransactionContext, - _addresses: &[std::string::String], - ) -> Result, ContextError> { - unimplemented!() - } - - fn get_state_entries( - &self, - addresses: &[String], - ) -> Result)>, ContextError> { - Ok(self - .state - .borrow() - .iter() - .filter(|(k, _)| addresses.contains(k)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect()) - } - - fn set_state_entries( - self: &TestTransactionContext, - entries: Vec<(String, Vec)>, - ) -> std::result::Result<(), sawtooth_sdk::processor::handler::ContextError> { - for entry in entries { - self.state.borrow_mut().insert(entry.0, entry.1); - } - - Ok(()) - } - } - - fn apply_transactions( - handler: &OpaTransactionHandler, - context: &mut TestTransactionContext, - transactions: &[sawtooth_sdk::messages::transaction::Transaction], - ) { - for tx in transactions { - let req = sawtooth_sdk::messages::processor::TpProcessRequest { - payload: tx.get_payload().to_vec(), - header: Some(protobuf::Message::parse_from_bytes(tx.get_header()).unwrap()).into(), - signature: tx.get_header_signature().to_string(), - ..Default::default() - }; - handler.apply(&req, context).unwrap(); - } - } - - fn get_sorted_transactions( - batch: &sawtooth_sdk::messages::batch::Batch, - ) -> Vec { - let mut transactions = batch.transactions.clone(); - transactions.sort_by_key(|tx| tx.header_signature.clone()); - transactions.to_vec() - } - - fn process_transactions( - transactions: &[sawtooth_sdk::messages::transaction::Transaction], - context: &mut TestTransactionContext, - handler: &OpaTransactionHandler, - ) -> (Vec<(String, Value)>, PrintableEvent) { - apply_transactions(handler, context, transactions); - (context.readable_state(), context.readable_events()) - } - - fn test_determinism( - transactions: &[sawtooth_sdk::messages::transaction::Transaction], - context: &TestTransactionContext, - number_of_determinism_checking_cycles: usize, - ) -> Vec<(Vec<(String, Value)>, PrintableEvent)> { - let handler = OpaTransactionHandler::new(); - - let contexts = (0..number_of_determinism_checking_cycles) - .map(|_| { - let mut context = context.clone(); - process_transactions(transactions, &mut context, &handler) - }) - .collect::>(); - - // Check if the contexts are the same after running apply - assert!( - contexts.iter().all(|context| contexts[0] == *context), - "All contexts must be the same after running apply. Contexts: {:?}", - contexts, - ); - - contexts - } - - fn assert_output_determinism( - expected_contexts: &[(Vec<(String, Value)>, PrintableEvent)], - readable_state_and_events: &(Vec<(String, Value)>, PrintableEvent), - ) { - // Check if the updated context is the same as the determinism check results - assert!( - expected_contexts - .iter() - .all(|context| readable_state_and_events == context), - "Updated context must be the same as previously run tests" - ); - } - - #[derive(Clone)] - struct WellBehavedBehavior { - handler: Arc, - context: Arc>, - } - - impl WellBehavedBehavior { - /// Submits a batch of transactions to the validator and performs determinism checks. - async fn submit_batch( - &self, - request: &[u8], - ) -> Result, SawtoothCommunicationError> { - // Parse the request into a `ClientBatchSubmitRequest` object and extract the first batch. - let req: ClientBatchSubmitRequest = - protobuf::Message::parse_from_bytes(request).unwrap(); - let batch = req.batches.into_iter().next().unwrap(); - - // Log some debug information about the batch and sort its transactions. - debug!(received_batch = ?batch, transactions = ?batch.transactions); - let transactions = get_sorted_transactions(&batch); - - // Get the current state and events before applying the transactions. - let preprocessing_state_and_events = { - let context = self.context.lock().unwrap(); - (context.readable_state(), context.readable_events()) - }; - - // Run the TP process in a seperate task, so it does not complete before subscriptions - // The simulation is only tacitly ordered - events sent before subscription will not be recieved - let self_clone = self.clone(); - tokio::task::spawn(async move { - tokio::time::sleep(Duration::from_secs(5)).await; - - // Perform determinism checking and get the expected contexts - let number_of_determinism_checking_cycles = 5; - let context = - { TestTransactionContext::clone(&self_clone.context.lock().unwrap()) }; - let expected_contexts = test_determinism( - transactions.as_slice(), - &context, - number_of_determinism_checking_cycles, - ); - - // Update the context and perform an output determinism check. - let mut context = self_clone.context.lock().unwrap(); - apply_transactions(&self_clone.handler, &mut context, transactions.as_slice()); - let updated_readable_state_and_events = - (context.readable_state(), context.readable_events()); - assert_ne!( - preprocessing_state_and_events, updated_readable_state_and_events, - "Context must be updated after running apply" - ); - assert_output_determinism(&expected_contexts, &updated_readable_state_and_events); - }); - // Create a response with an "OK" status and write it to a byte vector. - let mut response = ClientBatchSubmitResponse::default(); - response - .set_status(async_stl_client::messages::client_batch_submit_response::Status::Ok); - Ok(response.encode_to_vec()) - } - } - - #[async_trait::async_trait] - impl SimulatedSawtoothBehavior for WellBehavedBehavior { - #[instrument(skip(self, request))] - async fn handle_request( - &self, - message_type: MessageType, - request: Vec, - ) -> Result<(MessageType, Vec), SawtoothCommunicationError> { - match message_type { - // Batch submit request, decode and apply the transactions - // in the batch - MessageType::ClientBatchSubmitRequest => { - let buf = self.submit_batch(&request).await?; - Ok((MessageType::ClientBatchSubmitResponse, buf)) - } - // Always respond with a block height of one - MessageType::ClientBlockListRequest => { - let mut response = ClientBlockListResponse::default(); - let block_header = BlockHeader { - block_num: 1, - ..Default::default() - }; - let block_header_bytes = block_header.encode_to_vec(); - response.blocks = vec![async_stl_client::messages::Block { - header: block_header_bytes, - ..Default::default() - }]; - response.set_status( - async_stl_client::messages::client_block_list_response::Status::Ok, - ); - Ok(( - MessageType::ClientBlockListResponse, - response.encode_to_vec(), - )) - } - // We can just return Ok here, no need to fake routing - MessageType::ClientEventsSubscribeRequest => { - let mut response = ClientEventsSubscribeResponse::default(); - response.set_status( - async_stl_client::messages::client_events_subscribe_response::Status::Ok, - ); - Ok(( - MessageType::ClientEventsSubscribeResponse, - response.encode_to_vec(), - )) - } - MessageType::ClientStateGetRequest => { - let request = ClientStateGetRequest::decode(&*request).unwrap(); - let address = request.address; - - let state = self - .context - .lock() - .unwrap() - .get_state_entries(&[address]) - .unwrap(); - - let mut response = ClientStateGetResponse { - status: async_stl_client::messages::client_state_get_response::Status::Ok - as i32, - ..Default::default() - }; - - if state.is_empty() { - response.set_status(async_stl_client::messages::client_state_get_response::Status::NoResource); - } else { - response.value = state[0].1.clone(); - } - - let buf = response.encode_to_vec(); - Ok((MessageType::ClientStateGetResponse, buf)) - } - MessageType::ClientBlockGetByNumRequest => { - let req = ClientBlockGetByNumRequest::decode(&*request).unwrap(); - let mut response = ClientBlockGetResponse::default(); - let block_header = BlockHeader { - block_num: req.block_num, - previous_block_id: hex::encode([0; 32]), - ..Default::default() - }; - let block_header_bytes = block_header.encode_to_vec(); - response.block = Some(async_stl_client::messages::Block { - header: block_header_bytes, - ..Default::default() - }); - - response.set_status( - async_stl_client::messages::client_block_get_response::Status::Ok, - ); - let buf = response.encode_to_vec(); - Ok((MessageType::ClientBlockListResponse, buf)) - } - _ => panic!("Unexpected message type {} received", message_type as i32), - } - } - } - - struct EmbeddedOpaTp { - pub ledger: OpaLedger, - context: Arc>, - } - - impl EmbeddedOpaTp { - pub fn new_with_state( - state: BTreeMap>, - ) -> Result { - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - - let context = Arc::new(Mutex::new(TestTransactionContext::new_with_state( - tx, state, - ))); - - let handler = Arc::new(OpaTransactionHandler::new()); - - let behavior = WellBehavedBehavior { - handler, - context: context.clone(), - }; - - let listen_port = portpicker::pick_unused_port().expect("No ports free"); - let listen_addr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), listen_port); - let connect_addr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), listen_port); - - let behavior_clone = behavior; - thread::spawn(move || { - let rt = runtime::Builder::new_current_thread() - .enable_io() - .enable_time() - .build() - .unwrap(); - let mut rx = UnboundedReceiverStream::new(rx); - let local = tokio::task::LocalSet::new(); - - let task = local.run_until(async move { + use async_stl_client::{ + error::SawtoothCommunicationError, + ledger::SawtoothLedger, + messages::{ + message::MessageType, BlockHeader, ClientBatchSubmitResponse, + ClientBlockGetByNumRequest, ClientBlockGetResponse, ClientBlockListResponse, + ClientEventsSubscribeResponse, ClientStateGetRequest, ClientStateGetResponse, + }, + zmq_client::{HighestBlockValidatorSelector, ZmqRequestResponseSawtoothChannel}, + }; + use clap::ArgMatches; + use futures::{select, FutureExt, SinkExt, StreamExt}; + use opa_tp_protocol::{ + address::{FAMILY, VERSION}, + messages::OpaEvent, + state::OpaOperationEvent, + transaction::OpaSubmitTransaction, + }; + + use k256::{ + pkcs8::{EncodePrivateKey, LineEnding}, + SecretKey, + }; + use opa_tp::{abstract_tp::TP, tp::OpaTransactionHandler}; + + use prost::Message; + use rand::rngs::StdRng; + use rand_core::SeedableRng; + use sawtooth_sdk::{ + messages::client_batch_submit::ClientBatchSubmitRequest, + processor::handler::{ContextError, TransactionContext}, + }; + use serde_json::{self, Value}; + use tmq::{router, Context, Multipart}; + use tokio::runtime; + use uuid::Uuid; + + use std::{ + cell::RefCell, + collections::BTreeMap, + io::Write, + net::{Ipv4Addr, SocketAddr}, + sync::{Arc, Mutex}, + thread, + time::Duration, + }; + use tempfile::{NamedTempFile, TempDir}; + use tokio_stream::wrappers::UnboundedReceiverStream; + use tracing::{debug, error, info, instrument}; + + use crate::{cli, dispatch_args}; + + type TestTxEvents = Vec<(String, Vec<(String, String)>, Vec)>; + + #[async_trait::async_trait] + pub trait SimulatedSawtoothBehavior { + async fn handle_request( + &self, + message_type: MessageType, + request: Vec, + ) -> Result<(MessageType, Vec), SawtoothCommunicationError>; + } + + pub type OpaLedger = + SawtoothLedger; + + type PrintableEvent = Vec<(String, Vec<(String, String)>, Value)>; + + #[derive(Clone)] + pub struct TestTransactionContext { + pub state: RefCell>>, + pub events: RefCell, + tx: tokio::sync::mpsc::UnboundedSender)>>, + } + + impl TestTransactionContext { + pub fn new(tx: tokio::sync::mpsc::UnboundedSender)>>) -> Self { + Self { state: RefCell::new(BTreeMap::new()), events: RefCell::new(vec![]), tx } + } + + pub fn new_with_state( + tx: tokio::sync::mpsc::UnboundedSender)>>, + state: BTreeMap>, + ) -> Self { + Self { state: state.into(), events: RefCell::new(vec![]), tx } + } + + pub fn readable_state(&self) -> Vec<(String, Value)> { + // Deal with the fact that policies are raw bytes, but meta data and + // keys are json + self.state + .borrow() + .iter() + .map(|(k, v)| { + let as_string = String::from_utf8(v.clone()).unwrap(); + if serde_json::from_str::(&as_string).is_ok() { + (k.clone(), serde_json::from_str(&as_string).unwrap()) + } else { + (k.clone(), serde_json::to_value(v.clone()).unwrap()) + } + }) + .collect() + } + + pub fn readable_events(&self) -> PrintableEvent { + self.events + .borrow() + .iter() + .map(|(k, attr, data)| { + ( + k.clone(), + attr.clone(), + match &::decode(&**data) + .unwrap() + .payload + .unwrap() + { + opa_tp_protocol::messages::opa_event::Payload::Operation(operation) => + serde_json::from_str(operation).unwrap(), + opa_tp_protocol::messages::opa_event::Payload::Error(error) => + serde_json::from_str(error).unwrap(), + }, + ) + }) + .collect() + } + } + + impl sawtooth_sdk::processor::handler::TransactionContext for TestTransactionContext { + fn add_receipt_data( + self: &TestTransactionContext, + _data: &[u8], + ) -> Result<(), ContextError> { + unimplemented!() + } + + #[instrument(skip(self))] + fn add_event( + self: &TestTransactionContext, + event_type: String, + attributes: Vec<(String, String)>, + data: &[u8], + ) -> Result<(), ContextError> { + let stl_event = async_stl_client::messages::Event { + event_type: event_type.clone(), + attributes: attributes + .iter() + .map(|(k, v)| async_stl_client::messages::event::Attribute { + key: k.clone(), + value: v.clone(), + }) + .collect(), + data: data.to_vec(), + }; + + let list = async_stl_client::messages::EventList { events: vec![stl_event] }; + let stl_event: Vec = list.encode_to_vec(); + + self.tx.send(Some((MessageType::ClientEvents, stl_event))).unwrap(); + + self.events.borrow_mut().push((event_type, attributes, data.to_vec())); + + Ok(()) + } + + fn delete_state_entries( + self: &TestTransactionContext, + _addresses: &[std::string::String], + ) -> Result, ContextError> { + unimplemented!() + } + + fn get_state_entries( + &self, + addresses: &[String], + ) -> Result)>, ContextError> { + Ok(self + .state + .borrow() + .iter() + .filter(|(k, _)| addresses.contains(k)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect()) + } + + fn set_state_entries( + self: &TestTransactionContext, + entries: Vec<(String, Vec)>, + ) -> std::result::Result<(), sawtooth_sdk::processor::handler::ContextError> { + for entry in entries { + self.state.borrow_mut().insert(entry.0, entry.1); + } + + Ok(()) + } + } + + fn apply_transactions( + handler: &OpaTransactionHandler, + context: &mut TestTransactionContext, + transactions: &[sawtooth_sdk::messages::transaction::Transaction], + ) { + for tx in transactions { + let req = sawtooth_sdk::messages::processor::TpProcessRequest { + payload: tx.get_payload().to_vec(), + header: Some(protobuf::Message::parse_from_bytes(tx.get_header()).unwrap()).into(), + signature: tx.get_header_signature().to_string(), + ..Default::default() + }; + handler.apply(&req, context).unwrap(); + } + } + + fn get_sorted_transactions( + batch: &sawtooth_sdk::messages::batch::Batch, + ) -> Vec { + let mut transactions = batch.transactions.clone(); + transactions.sort_by_key(|tx| tx.header_signature.clone()); + transactions.to_vec() + } + + fn process_transactions( + transactions: &[sawtooth_sdk::messages::transaction::Transaction], + context: &mut TestTransactionContext, + handler: &OpaTransactionHandler, + ) -> (Vec<(String, Value)>, PrintableEvent) { + apply_transactions(handler, context, transactions); + (context.readable_state(), context.readable_events()) + } + + fn test_determinism( + transactions: &[sawtooth_sdk::messages::transaction::Transaction], + context: &TestTransactionContext, + number_of_determinism_checking_cycles: usize, + ) -> Vec<(Vec<(String, Value)>, PrintableEvent)> { + let handler = OpaTransactionHandler::new(); + + let contexts = (0..number_of_determinism_checking_cycles) + .map(|_| { + let mut context = context.clone(); + process_transactions(transactions, &mut context, &handler) + }) + .collect::>(); + + // Check if the contexts are the same after running apply + assert!( + contexts.iter().all(|context| contexts[0] == *context), + "All contexts must be the same after running apply. Contexts: {:?}", + contexts, + ); + + contexts + } + + fn assert_output_determinism( + expected_contexts: &[(Vec<(String, Value)>, PrintableEvent)], + readable_state_and_events: &(Vec<(String, Value)>, PrintableEvent), + ) { + // Check if the updated context is the same as the determinism check results + assert!( + expected_contexts.iter().all(|context| readable_state_and_events == context), + "Updated context must be the same as previously run tests" + ); + } + + #[derive(Clone)] + struct WellBehavedBehavior { + handler: Arc, + context: Arc>, + } + + impl WellBehavedBehavior { + /// Submits a batch of transactions to the validator and performs determinism checks. + async fn submit_batch( + &self, + request: &[u8], + ) -> Result, SawtoothCommunicationError> { + // Parse the request into a `ClientBatchSubmitRequest` object and extract the first + // batch. + let req: ClientBatchSubmitRequest = + protobuf::Message::parse_from_bytes(request).unwrap(); + let batch = req.batches.into_iter().next().unwrap(); + + // Log some debug information about the batch and sort its transactions. + debug!(received_batch = ?batch, transactions = ?batch.transactions); + let transactions = get_sorted_transactions(&batch); + + // Get the current state and events before applying the transactions. + let preprocessing_state_and_events = { + let context = self.context.lock().unwrap(); + (context.readable_state(), context.readable_events()) + }; + + // Run the TP process in a seperate task, so it does not complete before subscriptions + // The simulation is only tacitly ordered - events sent before subscription will not be + // recieved + let self_clone = self.clone(); + tokio::task::spawn(async move { + tokio::time::sleep(Duration::from_secs(5)).await; + + // Perform determinism checking and get the expected contexts + let number_of_determinism_checking_cycles = 5; + let context = + { TestTransactionContext::clone(&self_clone.context.lock().unwrap()) }; + let expected_contexts = test_determinism( + transactions.as_slice(), + &context, + number_of_determinism_checking_cycles, + ); + + // Update the context and perform an output determinism check. + let mut context = self_clone.context.lock().unwrap(); + apply_transactions(&self_clone.handler, &mut context, transactions.as_slice()); + let updated_readable_state_and_events = + (context.readable_state(), context.readable_events()); + assert_ne!( + preprocessing_state_and_events, updated_readable_state_and_events, + "Context must be updated after running apply" + ); + assert_output_determinism(&expected_contexts, &updated_readable_state_and_events); + }); + // Create a response with an "OK" status and write it to a byte vector. + let mut response = ClientBatchSubmitResponse::default(); + response + .set_status(async_stl_client::messages::client_batch_submit_response::Status::Ok); + Ok(response.encode_to_vec()) + } + } + + #[async_trait::async_trait] + impl SimulatedSawtoothBehavior for WellBehavedBehavior { + #[instrument(skip(self, request))] + async fn handle_request( + &self, + message_type: MessageType, + request: Vec, + ) -> Result<(MessageType, Vec), SawtoothCommunicationError> { + match message_type { + // Batch submit request, decode and apply the transactions + // in the batch + MessageType::ClientBatchSubmitRequest => { + let buf = self.submit_batch(&request).await?; + Ok((MessageType::ClientBatchSubmitResponse, buf)) + }, + // Always respond with a block height of one + MessageType::ClientBlockListRequest => { + let mut response = ClientBlockListResponse::default(); + let block_header = BlockHeader { block_num: 1, ..Default::default() }; + let block_header_bytes = block_header.encode_to_vec(); + response.blocks = vec![async_stl_client::messages::Block { + header: block_header_bytes, + ..Default::default() + }]; + response.set_status( + async_stl_client::messages::client_block_list_response::Status::Ok, + ); + Ok((MessageType::ClientBlockListResponse, response.encode_to_vec())) + }, + // We can just return Ok here, no need to fake routing + MessageType::ClientEventsSubscribeRequest => { + let mut response = ClientEventsSubscribeResponse::default(); + response.set_status( + async_stl_client::messages::client_events_subscribe_response::Status::Ok, + ); + Ok((MessageType::ClientEventsSubscribeResponse, response.encode_to_vec())) + }, + MessageType::ClientStateGetRequest => { + let request = ClientStateGetRequest::decode(&*request).unwrap(); + let address = request.address; + + let state = self.context.lock().unwrap().get_state_entries(&[address]).unwrap(); + + let mut response = ClientStateGetResponse { + status: async_stl_client::messages::client_state_get_response::Status::Ok + as i32, + ..Default::default() + }; + + if state.is_empty() { + response.set_status(async_stl_client::messages::client_state_get_response::Status::NoResource); + } else { + response.value = state[0].1.clone(); + } + + let buf = response.encode_to_vec(); + Ok((MessageType::ClientStateGetResponse, buf)) + }, + MessageType::ClientBlockGetByNumRequest => { + let req = ClientBlockGetByNumRequest::decode(&*request).unwrap(); + let mut response = ClientBlockGetResponse::default(); + let block_header = BlockHeader { + block_num: req.block_num, + previous_block_id: hex::encode([0; 32]), + ..Default::default() + }; + let block_header_bytes = block_header.encode_to_vec(); + response.block = Some(async_stl_client::messages::Block { + header: block_header_bytes, + ..Default::default() + }); + + response.set_status( + async_stl_client::messages::client_block_get_response::Status::Ok, + ); + let buf = response.encode_to_vec(); + Ok((MessageType::ClientBlockListResponse, buf)) + }, + _ => panic!("Unexpected message type {} received", message_type as i32), + } + } + } + + struct EmbeddedOpaTp { + pub ledger: OpaLedger, + context: Arc>, + } + + impl EmbeddedOpaTp { + pub fn new_with_state( + state: BTreeMap>, + ) -> Result { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + + let context = Arc::new(Mutex::new(TestTransactionContext::new_with_state(tx, state))); + + let handler = Arc::new(OpaTransactionHandler::new()); + + let behavior = WellBehavedBehavior { handler, context: context.clone() }; + + let listen_port = portpicker::pick_unused_port().expect("No ports free"); + let listen_addr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), listen_port); + let connect_addr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), listen_port); + + let behavior_clone = behavior; + thread::spawn(move || { + let rt = runtime::Builder::new_current_thread() + .enable_io() + .enable_time() + .build() + .unwrap(); + let mut rx = UnboundedReceiverStream::new(rx); + let local = tokio::task::LocalSet::new(); + + let task = local.run_until(async move { tokio::task::spawn_local(async move { let (mut router_tx, mut router_rx) = router(&Context::new()) .bind(&format!("tcp://{}", listen_addr)) @@ -1014,78 +975,76 @@ pub mod test { }) .await }); - rt.block_on(task).ok(); - }); - - let channel = ZmqRequestResponseSawtoothChannel::new( - &format!("test_{}", Uuid::new_v4()), - &[connect_addr], - HighestBlockValidatorSelector, - )?; - - Ok(Self { - ledger: OpaLedger::new(channel, FAMILY, VERSION), - context, - }) - } - - pub fn readable_state(&self) -> Vec<(String, Value)> { - self.context.lock().unwrap().readable_state() - } - - pub fn new() -> Self { - EmbeddedOpaTp::new_with_state(BTreeMap::new()).unwrap() - } - } - - fn embed_opa_tp() -> EmbeddedOpaTp { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - EmbeddedOpaTp::new() - } - - fn reuse_opa_tp_state(tp: EmbeddedOpaTp) -> EmbeddedOpaTp { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - EmbeddedOpaTp::new_with_state(tp.context.lock().unwrap().state.borrow().clone()).unwrap() - } - - fn get_opactl_cmd(command_line: &str) -> ArgMatches { - let cli = cli::cli(); - cli.get_matches_from(command_line.split_whitespace()) - } - - fn key_from_seed(seed: u8) -> String { - let secret: SecretKey = SecretKey::random(StdRng::from_seed([seed; 32])); - secret.to_pkcs8_pem(LineEnding::CRLF).unwrap().to_string() - } - - // Cli should automatically create ephemeral batcher keys, but we need to supply named keyfiles in a temp directory - async fn bootstrap_root_state() -> (String, EmbeddedOpaTp, TempDir) { - let root_key = key_from_seed(0); - - let keystore = tempfile::tempdir().unwrap(); - let keyfile_path = keystore.path().join("./opa-pk"); - std::fs::write(&keyfile_path, root_key.as_bytes()).unwrap(); - - let matches = get_opactl_cmd(&format!( - "opactl --batcher-key-generated --keystore-path {} bootstrap", - keystore.path().display() - )); - - let opa_tp = embed_opa_tp(); - - tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; - dispatch_args(matches, opa_tp.ledger.clone(), opa_tp.ledger.clone()) - .await - .unwrap(); - - (root_key, reuse_opa_tp_state(opa_tp), keystore) - } - - #[tokio::test] - async fn bootstrap_root_and_get_key() { - let (_root_key, opa_tp, _keystore) = bootstrap_root_state().await; - //Generate a key pem and set env vars - insta::assert_yaml_snapshot!(opa_tp.readable_state(), { + rt.block_on(task).ok(); + }); + + let channel = ZmqRequestResponseSawtoothChannel::new( + &format!("test_{}", Uuid::new_v4()), + &[connect_addr], + HighestBlockValidatorSelector, + )?; + + Ok(Self { ledger: OpaLedger::new(channel, FAMILY, VERSION), context }) + } + + pub fn readable_state(&self) -> Vec<(String, Value)> { + self.context.lock().unwrap().readable_state() + } + + pub fn new() -> Self { + EmbeddedOpaTp::new_with_state(BTreeMap::new()).unwrap() + } + } + + fn embed_opa_tp() -> EmbeddedOpaTp { + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + EmbeddedOpaTp::new() + } + + fn reuse_opa_tp_state(tp: EmbeddedOpaTp) -> EmbeddedOpaTp { + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + EmbeddedOpaTp::new_with_state(tp.context.lock().unwrap().state.borrow().clone()).unwrap() + } + + fn get_opactl_cmd(command_line: &str) -> ArgMatches { + let cli = cli::cli(); + cli.get_matches_from(command_line.split_whitespace()) + } + + fn key_from_seed(seed: u8) -> String { + let secret: SecretKey = SecretKey::random(StdRng::from_seed([seed; 32])); + secret.to_pkcs8_pem(LineEnding::CRLF).unwrap().to_string() + } + + // Cli should automatically create ephemeral batcher keys, but we need to supply named keyfiles + // in a temp directory + async fn bootstrap_root_state() -> (String, EmbeddedOpaTp, TempDir) { + let root_key = key_from_seed(0); + + let keystore = tempfile::tempdir().unwrap(); + let keyfile_path = keystore.path().join("./opa-pk"); + std::fs::write(&keyfile_path, root_key.as_bytes()).unwrap(); + + let matches = get_opactl_cmd(&format!( + "opactl --batcher-key-generated --keystore-path {} bootstrap", + keystore.path().display() + )); + + let opa_tp = embed_opa_tp(); + + tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; + dispatch_args(matches, opa_tp.ledger.clone(), opa_tp.ledger.clone()) + .await + .unwrap(); + + (root_key, reuse_opa_tp_state(opa_tp), keystore) + } + + #[tokio::test] + async fn bootstrap_root_and_get_key() { + let (_root_key, opa_tp, _keystore) = bootstrap_root_state().await; + //Generate a key pem and set env vars + insta::assert_yaml_snapshot!(opa_tp.readable_state(), { ".**.date" => "[date]", ".**.key" => "[pem]", } ,@r###" @@ -1098,35 +1057,35 @@ pub mod test { id: root "###); - let opa_tp = reuse_opa_tp_state(opa_tp); + let opa_tp = reuse_opa_tp_state(opa_tp); - tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; + tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; - let out_keyfile = NamedTempFile::new().unwrap(); + let out_keyfile = NamedTempFile::new().unwrap(); - let matches = get_opactl_cmd( - format!("opactl get-key --output {}", out_keyfile.path().display(),).as_str(), - ); + let matches = get_opactl_cmd( + format!("opactl get-key --output {}", out_keyfile.path().display(),).as_str(), + ); - insta::assert_yaml_snapshot!( + insta::assert_yaml_snapshot!( dispatch_args(matches, opa_tp.ledger.clone(), opa_tp.ledger.clone()) .await .unwrap().0, @r###" --- NoWait "###); - } + } - #[tokio::test] - async fn rotate_root() { - let (_root_key, opa_tp, keystore) = bootstrap_root_state().await; + #[tokio::test] + async fn rotate_root() { + let (_root_key, opa_tp, keystore) = bootstrap_root_state().await; - let new_root_key = key_from_seed(1); + let new_root_key = key_from_seed(1); - let keyfile_path = keystore.path().join("./new-root-1"); - std::fs::write(&keyfile_path, new_root_key.as_bytes()).unwrap(); + let keyfile_path = keystore.path().join("./new-root-1"); + std::fs::write(&keyfile_path, new_root_key.as_bytes()).unwrap(); - let matches = get_opactl_cmd( + let matches = get_opactl_cmd( format!( "opactl --batcher-key-generated --opa-key-from-path --keystore-path {} rotate-root --new-root-key new-root-1", keystore.path().display(), @@ -1134,7 +1093,7 @@ pub mod test { .as_str(), ); - insta::assert_yaml_snapshot!( + insta::assert_yaml_snapshot!( dispatch_args(matches, opa_tp.ledger.clone(), opa_tp.ledger.clone()) .await .unwrap().0, { @@ -1153,7 +1112,7 @@ pub mod test { version: 0 "###); - insta::assert_yaml_snapshot!(opa_tp.readable_state(),{ + insta::assert_yaml_snapshot!(opa_tp.readable_state(),{ ".**.date" => "[date]", ".**.key" => "[pem]", }, @r###" @@ -1167,18 +1126,18 @@ pub mod test { version: 0 id: root "###); - } + } - #[tokio::test] - async fn register_and_rotate_key() { - let (_root_key, opa_tp, keystore) = bootstrap_root_state().await; + #[tokio::test] + async fn register_and_rotate_key() { + let (_root_key, opa_tp, keystore) = bootstrap_root_state().await; - let new_key = key_from_seed(1); + let new_key = key_from_seed(1); - let keyfile_path = keystore.path().join("./new-key-1"); - std::fs::write(&keyfile_path, new_key.as_bytes()).unwrap(); + let keyfile_path = keystore.path().join("./new-key-1"); + std::fs::write(&keyfile_path, new_key.as_bytes()).unwrap(); - let matches = get_opactl_cmd( + let matches = get_opactl_cmd( format!( "opactl --batcher-key-generated --keystore-path {} register-key --new-key new-key-1 --id test", keystore.path().display(), @@ -1186,7 +1145,7 @@ pub mod test { .as_str(), ); - insta::assert_yaml_snapshot!( + insta::assert_yaml_snapshot!( dispatch_args(matches, opa_tp.ledger.clone(), opa_tp.ledger.clone()) .await .unwrap().0, { @@ -1203,7 +1162,7 @@ pub mod test { expired: ~ "###); - insta::assert_yaml_snapshot!(opa_tp.readable_state(), { + insta::assert_yaml_snapshot!(opa_tp.readable_state(), { ".**.date" => "[date]", ".**.key" => "[pem]", }, @r###" @@ -1222,12 +1181,12 @@ pub mod test { id: test "###); - let new_key_2 = key_from_seed(1); + let new_key_2 = key_from_seed(1); - let keyfile_path = keystore.path().join("./new-key-2"); - std::fs::write(&keyfile_path, new_key_2.as_bytes()).unwrap(); + let keyfile_path = keystore.path().join("./new-key-2"); + std::fs::write(&keyfile_path, new_key_2.as_bytes()).unwrap(); - let matches = get_opactl_cmd( + let matches = get_opactl_cmd( format!( "opactl --batcher-key-generated --keystore-path {} rotate-key --current-key new-key-1 --new-key new-key-2 --id test", keystore.path().display(), @@ -1235,9 +1194,9 @@ pub mod test { .as_str(), ); - let opa_tp = reuse_opa_tp_state(opa_tp); + let opa_tp = reuse_opa_tp_state(opa_tp); - insta::assert_yaml_snapshot!( + insta::assert_yaml_snapshot!( dispatch_args(matches, opa_tp.ledger.clone(), opa_tp.ledger.clone()) .await .unwrap().0, { @@ -1256,7 +1215,7 @@ pub mod test { version: 0 "###); - insta::assert_yaml_snapshot!(opa_tp.readable_state(), { + insta::assert_yaml_snapshot!(opa_tp.readable_state(), { ".**.date" => "[date]", ".**.key" => "[pem]", } ,@r###" @@ -1276,19 +1235,19 @@ pub mod test { version: 0 id: test "###); - } + } - #[tokio::test] - async fn set_and_update_policy() { - let (root_key, opa_tp, keystore) = bootstrap_root_state().await; + #[tokio::test] + async fn set_and_update_policy() { + let (root_key, opa_tp, keystore) = bootstrap_root_state().await; - let mut root_keyfile = NamedTempFile::new().unwrap(); - root_keyfile.write_all(root_key.as_bytes()).unwrap(); + let mut root_keyfile = NamedTempFile::new().unwrap(); + root_keyfile.write_all(root_key.as_bytes()).unwrap(); - let mut policy = NamedTempFile::new().unwrap(); - policy.write_all(&[0]).unwrap(); + let mut policy = NamedTempFile::new().unwrap(); + policy.write_all(&[0]).unwrap(); - let matches = get_opactl_cmd( + let matches = get_opactl_cmd( format!( "opactl --batcher-key-generated --keystore-path {} set-policy --id test --policy {}", keystore.path().display(), @@ -1297,7 +1256,7 @@ pub mod test { .as_str(), ); - insta::assert_yaml_snapshot!(dispatch_args( + insta::assert_yaml_snapshot!(dispatch_args( matches, opa_tp.ledger.clone(), opa_tp.ledger.clone() @@ -1314,7 +1273,7 @@ pub mod test { policy_address: 7ed1931c262a4be700b69974438a35ae56a07ce96778b276c8a061dc254d9862c7ecff "###); - insta::assert_yaml_snapshot!(opa_tp.readable_state(), { + insta::assert_yaml_snapshot!(opa_tp.readable_state(), { ".**.date" => "[date]", ".**.key" => "[pem]" } ,@r###" @@ -1333,9 +1292,9 @@ pub mod test { policy_address: 7ed1931c262a4be700b69974438a35ae56a07ce96778b276c8a061dc254d9862c7ecff "###); - policy.write_all(&[1]).unwrap(); + policy.write_all(&[1]).unwrap(); - let matches = get_opactl_cmd( + let matches = get_opactl_cmd( format!( "opactl --batcher-key-generated --keystore-path {} set-policy --id test --policy {}", keystore.path().display(), @@ -1344,9 +1303,9 @@ pub mod test { .as_str(), ); - let opa_tp = reuse_opa_tp_state(opa_tp); + let opa_tp = reuse_opa_tp_state(opa_tp); - insta::assert_yaml_snapshot!(dispatch_args(matches, opa_tp.ledger.clone(), opa_tp.ledger.clone()) + insta::assert_yaml_snapshot!(dispatch_args(matches, opa_tp.ledger.clone(), opa_tp.ledger.clone()) .await .unwrap().0, { ".**.date" => "[date]" @@ -1359,7 +1318,7 @@ pub mod test { policy_address: 7ed1931c262a4be700b69974438a35ae56a07ce96778b276c8a061dc254d9862c7ecff "### ); - insta::assert_yaml_snapshot!(opa_tp.readable_state(), { + insta::assert_yaml_snapshot!(opa_tp.readable_state(), { ".**.date" => "[date]", ".**.key" => "[pem]" } ,@r###" @@ -1378,5 +1337,5 @@ pub mod test { id: test policy_address: 7ed1931c262a4be700b69974438a35ae56a07ce96778b276c8a061dc254d9862c7ecff "###); - } + } } diff --git a/crates/pallet-chronicle/Cargo.toml b/crates/pallet-chronicle/Cargo.toml new file mode 100644 index 000000000..6b28e7212 --- /dev/null +++ b/crates/pallet-chronicle/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "pallet-chronicle" +edition = "2021" +version = "0.7.5" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +common = {path = "../common", default-features=false, features=["parity"]} +parity-scale-codec = { version="3.6.5", default-features = false, features = ["derive"] } +uuid = {version="1.5.0", default-features=false} +sp-std = { version="11.0.0", default-features = false} +scale-info = { version="2.10.0", default-features = false, features = ["derive"] } +frame-support = { version="24.0.0", default-features=false } +frame-system = { version= "24.0.0",default-features=false } +frame-benchmarking = { version = "24.0.0", default-features = false, optional=true} +tracing = {version="0.1.40", default-features=false, features=["attributes"]} +newtype-derive-2018 = {workspace=true} +macro-attr-2018 = {workspace=true} + +[dev-dependencies] +sp-runtime = { version="27.0.0"} +sp-io = {version="26.0.0" } +sp-core = {version="24.0.0"} +chronicle-telemetry = {path="../chronicle-telemetry"} + +[features] +default = ["std"] +std = [ + "common/std", + "parity-scale-codec/std", + "uuid/std", + "sp-std/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", + "tracing/std", +] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/crates/pallet-chronicle/src/lib.rs b/crates/pallet-chronicle/src/lib.rs new file mode 100644 index 000000000..ff00c99ca --- /dev/null +++ b/crates/pallet-chronicle/src/lib.rs @@ -0,0 +1,185 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +/// Re-export types required for runtime +pub use common::prov::*; + +use common::ledger::LedgerAddress; +/// Edit this file to define custom logic or remove it if it is not needed. +/// Learn more about FRAME and the core library of Substrate FRAME pallets: +/// +pub use pallet::*; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; +pub use common::prov::*; +pub use weights::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_std::collections::btree_set::BTreeSet; + use sp_std::vec::Vec; + + #[pallet::pallet] + pub struct Pallet(_); + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Type representing the weight of this pallet + type WeightInfo: WeightInfo; + + type OperationList: Parameter + + Into> + + From> + + parity_scale_codec::Codec; + } + // The pallet's runtime storage items. + // https://docs.substrate.io/main-docs/build/runtime-storage/ + #[pallet::storage] + #[pallet::getter(fn prov)] + // Learn more about declaring storage items: + // https://docs.substrate.io/main-docs/build/runtime-storage/#declaring-storage-items + pub type Provenance = StorageMap<_, Twox128, LedgerAddress, common::prov::ProvModel>; + + // Pallets use events to inform users when important changes are made. + // https://docs.substrate.io/main-docs/build/events-errors/ + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Applied(common::prov::ProvModel), + Contradiction(common::prov::Contradiction), + } + + // Errors inform users that something went wrong. + #[pallet::error] + pub enum Error { + Address, + Contradiction, + Compaction, + Expansion, + Identity, + IRef, + NotAChronicleIri, + MissingId, + MissingProperty, + NotANode, + NotAnObject, + OpaExecutor, + SerdeJson, + SubmissionFormat, + Time, + Tokio, + Utf8, + } + + impl From for Error { + fn from(error: common::prov::ProcessorError) -> Self { + match error { + common::prov::ProcessorError::Address => Error::Address, + common::prov::ProcessorError::Contradiction { .. } => Error::Contradiction, + common::prov::ProcessorError::Expansion { .. } => Error::Expansion, + common::prov::ProcessorError::Identity(_) => Error::Identity, + common::prov::ProcessorError::NotAChronicleIri { .. } => Error::NotAChronicleIri, + common::prov::ProcessorError::MissingId { .. } => Error::MissingId, + common::prov::ProcessorError::MissingProperty { .. } => Error::MissingProperty, + common::prov::ProcessorError::NotANode(_) => Error::NotANode, + common::prov::ProcessorError::NotAnObject => Error::NotAnObject, + common::prov::ProcessorError::OpaExecutor(_) => Error::OpaExecutor, + common::prov::ProcessorError::SerdeJson(_) => Error::SerdeJson, + common::prov::ProcessorError::SubmissionFormat(_) => Error::SubmissionFormat, + common::prov::ProcessorError::Time(_) => Error::Time, + common::prov::ProcessorError::Tokio => Error::Tokio, + common::prov::ProcessorError::Utf8(_) => Error::Utf8, + _ => unreachable!(), //TODO: NOT THIS + } + } + } + + // Dispatchable functions allows users to interact with the pallet and invoke state changes. + // These functions materialize as "extrinsics", which are often compared to transactions. + // Dispatchable functions must be annotated with a weight and must return a DispatchResult. + #[pallet::call] + impl Pallet { + /// An example dispatchable that takes a singles value as a parameter, writes the value to + /// storage and emits an event. This function must be dispatched by a signed extrinsic. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::apply())] + pub fn apply(origin: OriginFor, operations: T::OperationList) -> DispatchResult { + // Check that the extrinsic was signed and get the signer. + // This function will return an error if the extrinsic is not signed. + // https://docs.substrate.io/main-docs/build/origins/ + let who = ensure_signed(origin)?; + + // Get operations and load their dependencies + let ops: Vec = operations.into(); + + let deps = ops.iter().flat_map(|tx| tx.dependencies()).collect::>(); + + let initial_input_models: Vec<_> = deps + .into_iter() + .map(|addr| (addr.clone(), Provenance::::get(&addr))) + .collect(); + + let mut state: common::ledger::OperationState = common::ledger::OperationState::new(); + + state.update_state(initial_input_models.into_iter()); + + let mut model = common::prov::ProvModel::default(); + + for op in ops { + let res = op.process(model, state.input()); + match res { + // A contradiction raises an event, not an error and shortcuts processing - contradiction attempts are useful provenance + // and should not be a purely operational concern + Err(common::prov::ProcessorError::Contradiction(source)) => { + tracing::info!(contradiction = %source); + + Self::deposit_event(Event::::Contradiction(source)); + + return Ok(()); + }, + // Severe errors should be logged + Err(e) => { + tracing::error!(chronicle_prov_failure = %e); + + return Err(Error::::from(e).into()); + }, + Ok((tx_output, updated_model)) => { + state.update_state_from_output(tx_output.into_iter()); + model = updated_model; + }, + } + } + + // Compute delta + let dirty = state.dirty().collect::>(); + + tracing::trace!(dirty = ?dirty); + + let mut delta = common::prov::ProvModel::default(); + for common::ledger::StateOutput { address, data } in dirty { + delta.combine(&data); + + // Update storage. + Provenance::::set(&address, Some(data)); + } + + // Emit an event. + Self::deposit_event(Event::Applied(delta)); + // Return a successful DispatchResultWithPostInfo + Ok(()) + } + } +} diff --git a/crates/frame-chronicle/src/mock.rs b/crates/pallet-chronicle/src/mock.rs similarity index 100% rename from crates/frame-chronicle/src/mock.rs rename to crates/pallet-chronicle/src/mock.rs diff --git a/crates/pallet-chronicle/src/tests.rs b/crates/pallet-chronicle/src/tests.rs new file mode 100644 index 000000000..3d9b18b95 --- /dev/null +++ b/crates/pallet-chronicle/src/tests.rs @@ -0,0 +1,43 @@ +use crate::{mock::*, Event}; +use common::prov::{ + operations::{ChronicleOperation, CreateNamespace}, + ExternalId, NamespaceId, +}; +use frame_support::assert_ok; +use uuid::Uuid; + +#[test] +fn it_works_for_default_value() { + new_test_ext().execute_with(|| { + // Go past genesis block so events get deposited + System::set_block_number(1); + // Dispatch a signed extrinsic. + assert_ok!(ChronicleModule::apply(RuntimeOrigin::signed(1), vec![])); + // Assert that the correct event was deposited + System::assert_last_event(Event::Applied(common::prov::ProvModel::default()).into()); + }); +} + +#[test] +fn single_operation() { + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + new_test_ext().execute_with(|| { + // Go past genesis block so events get deposited + System::set_block_number(1); + let uuid = Uuid::new_v4(); + let op = ChronicleOperation::CreateNamespace(CreateNamespace { + id: NamespaceId::from_external_id("test", uuid), + external_id: ExternalId::from("test"), + uuid: uuid.into(), + }); + // Dispatch our operation + assert_ok!(ChronicleModule::apply(RuntimeOrigin::signed(1), vec![op.clone()])); + + // Apply that operation to a new prov model for assertion - + // the pallet execution should produce an identical delta + let mut delta_model = common::prov::ProvModel::default(); + delta_model.apply(&op).unwrap(); + // Assert that the delta is correct + System::assert_last_event(Event::Applied(delta_model).into()); + }); +} diff --git a/crates/frame-chronicle/src/weights.rs b/crates/pallet-chronicle/src/weights.rs similarity index 100% rename from crates/frame-chronicle/src/weights.rs rename to crates/pallet-chronicle/src/weights.rs diff --git a/crates/pallet-chronicle/tree.txt b/crates/pallet-chronicle/tree.txt new file mode 100644 index 000000000..c4b35efba --- /dev/null +++ b/crates/pallet-chronicle/tree.txt @@ -0,0 +1,1257 @@ +pallet-chronicle v0.7.5 (/Users/ryan/code/chronicle/crates/pallet-chronicle) +├── common v0.7.5 (/Users/ryan/code/chronicle/crates/common) +│ ├── anyhow v1.0.75 +│ ├── async-trait v0.1.74 (proc-macro) +│ │ ├── proc-macro2 v1.0.69 +│ │ │ └── unicode-ident v1.0.12 +│ │ ├── quote v1.0.33 +│ │ │ └── proc-macro2 v1.0.69 (*) +│ │ └── syn v2.0.38 +│ │ ├── proc-macro2 v1.0.69 (*) +│ │ ├── quote v1.0.33 (*) +│ │ └── unicode-ident v1.0.12 +│ ├── chrono v0.4.31 +│ │ ├── js-sys v0.3.64 +│ │ │ └── wasm-bindgen v0.2.87 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ └── wasm-bindgen-macro v0.2.87 (proc-macro) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── wasm-bindgen-macro-support v0.2.87 +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ ├── syn v2.0.38 (*) +│ │ │ ├── wasm-bindgen-backend v0.2.87 +│ │ │ │ ├── bumpalo v3.14.0 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ ├── syn v2.0.38 (*) +│ │ │ │ └── wasm-bindgen-shared v0.2.87 +│ │ │ └── wasm-bindgen-shared v0.2.87 +│ │ ├── num-traits v0.2.17 +│ │ │ [build-dependencies] +│ │ │ └── autocfg v1.1.0 +│ │ ├── serde v1.0.190 +│ │ │ └── serde_derive v1.0.190 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v2.0.38 (*) +│ │ └── wasm-bindgen v0.2.87 (*) +│ ├── futures v0.3.29 +│ │ ├── futures-channel v0.3.29 +│ │ │ ├── futures-core v0.3.29 +│ │ │ └── futures-sink v0.3.29 +│ │ ├── futures-core v0.3.29 +│ │ ├── futures-executor v0.3.29 +│ │ │ ├── futures-core v0.3.29 +│ │ │ ├── futures-task v0.3.29 +│ │ │ ├── futures-util v0.3.29 +│ │ │ │ ├── futures-channel v0.3.29 (*) +│ │ │ │ ├── futures-core v0.3.29 +│ │ │ │ ├── futures-io v0.3.29 +│ │ │ │ ├── futures-macro v0.3.29 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── futures-sink v0.3.29 +│ │ │ │ ├── futures-task v0.3.29 +│ │ │ │ ├── memchr v2.6.4 +│ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ ├── pin-utils v0.1.0 +│ │ │ │ └── slab v0.4.9 +│ │ │ │ [build-dependencies] +│ │ │ │ └── autocfg v1.1.0 +│ │ │ └── num_cpus v1.16.0 +│ │ │ └── libc v0.2.149 +│ │ ├── futures-io v0.3.29 +│ │ ├── futures-sink v0.3.29 +│ │ ├── futures-task v0.3.29 +│ │ └── futures-util v0.3.29 (*) +│ ├── glob v0.3.1 +│ ├── hex v0.4.3 +│ ├── iref v2.2.3 +│ │ ├── pct-str v1.2.0 +│ │ │ └── utf8-decode v1.0.1 +│ │ └── smallvec v1.11.1 +│ ├── iref-enum v2.1.0 (proc-macro) +│ │ ├── iref v2.2.3 +│ │ │ ├── pct-str v1.2.0 +│ │ │ │ └── utf8-decode v1.0.1 +│ │ │ └── smallvec v1.11.1 +│ │ ├── proc-macro2 v1.0.69 (*) +│ │ ├── quote v1.0.33 (*) +│ │ └── syn v1.0.109 +│ │ ├── proc-macro2 v1.0.69 (*) +│ │ ├── quote v1.0.33 (*) +│ │ └── unicode-ident v1.0.12 +│ ├── k256 v0.11.6 +│ │ ├── cfg-if v1.0.0 +│ │ ├── ecdsa v0.14.8 +│ │ │ ├── der v0.6.1 +│ │ │ │ └── const-oid v0.9.5 +│ │ │ ├── elliptic-curve v0.12.3 +│ │ │ │ ├── base16ct v0.1.1 +│ │ │ │ ├── crypto-bigint v0.4.9 +│ │ │ │ │ ├── generic-array v0.14.7 +│ │ │ │ │ │ ├── typenum v1.17.0 +│ │ │ │ │ │ └── zeroize v1.6.0 +│ │ │ │ │ │ └── zeroize_derive v1.4.2 (proc-macro) +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── version_check v0.9.4 +│ │ │ │ │ ├── rand_core v0.6.4 +│ │ │ │ │ │ └── getrandom v0.2.10 +│ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── der v0.6.1 (*) +│ │ │ │ ├── digest v0.10.7 +│ │ │ │ │ ├── block-buffer v0.10.4 +│ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ ├── const-oid v0.9.5 +│ │ │ │ │ ├── crypto-common v0.1.6 +│ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ └── typenum v1.17.0 +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ ├── ff v0.12.1 +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ ├── group v0.12.1 +│ │ │ │ │ ├── ff v0.12.1 (*) +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ ├── pkcs8 v0.9.0 +│ │ │ │ │ ├── der v0.6.1 (*) +│ │ │ │ │ └── spki v0.6.0 +│ │ │ │ │ └── der v0.6.1 (*) +│ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ ├── sec1 v0.3.0 +│ │ │ │ │ ├── base16ct v0.1.1 +│ │ │ │ │ ├── der v0.6.1 (*) +│ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── subtle v2.4.1 +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── rfc6979 v0.3.1 +│ │ │ │ ├── crypto-bigint v0.4.9 (*) +│ │ │ │ ├── hmac v0.12.1 +│ │ │ │ │ └── digest v0.10.7 (*) +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ └── signature v1.6.4 +│ │ │ ├── digest v0.10.7 (*) +│ │ │ └── rand_core v0.6.4 (*) +│ │ ├── elliptic-curve v0.12.3 (*) +│ │ └── sha2 v0.10.8 +│ │ ├── cfg-if v1.0.0 +│ │ └── digest v0.10.7 (*) +│ ├── lazy_static v1.4.0 +│ ├── locspan v0.7.16 +│ ├── macro-attr-2018 v3.0.0 +│ ├── mime v0.3.17 +│ ├── newtype-derive-2018 v0.2.1 +│ │ └── generics v0.5.1 +│ ├── parity-scale-codec v3.6.5 +│ │ ├── arrayvec v0.7.4 +│ │ ├── byte-slice-cast v1.2.2 +│ │ ├── bytes v1.5.0 +│ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ ├── parity-scale-codec-derive v3.6.5 (proc-macro) +│ │ │ ├── proc-macro-crate v1.1.3 +│ │ │ │ ├── thiserror v1.0.50 +│ │ │ │ │ └── thiserror-impl v1.0.50 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ └── toml v0.5.11 +│ │ │ │ └── serde v1.0.190 +│ │ │ │ └── serde_derive v1.0.190 (proc-macro) (*) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ └── serde v1.0.190 (*) +│ ├── percent-encoding v2.3.0 +│ ├── scale-info v2.10.0 +│ │ ├── cfg-if v1.0.0 +│ │ ├── derive_more v0.99.17 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info-derive v2.10.0 (proc-macro) +│ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ └── serde v1.0.190 (*) +│ ├── serde v1.0.190 (*) +│ ├── serde_derive v1.0.190 (proc-macro) (*) +│ ├── serde_json v1.0.108 +│ │ ├── itoa v1.0.9 +│ │ ├── ryu v1.0.15 +│ │ └── serde v1.0.190 (*) +│ ├── static-iref v2.0.0 (proc-macro) +│ │ └── iref v2.2.3 (*) +│ ├── thiserror v1.0.50 +│ │ └── thiserror-impl v1.0.50 (proc-macro) (*) +│ ├── thiserror-no-std v2.0.2 +│ │ └── thiserror-impl-no-std v2.0.2 (proc-macro) +│ │ ├── proc-macro2 v1.0.69 (*) +│ │ ├── quote v1.0.33 (*) +│ │ └── syn v1.0.109 (*) +│ ├── tracing v0.1.40 +│ │ ├── pin-project-lite v0.2.13 +│ │ ├── tracing-attributes v0.1.27 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v2.0.38 (*) +│ │ └── tracing-core v0.1.32 +│ │ └── once_cell v1.18.0 +│ ├── url v2.4.1 +│ │ ├── form_urlencoded v1.2.0 +│ │ │ └── percent-encoding v2.3.0 +│ │ ├── idna v0.4.0 +│ │ │ ├── unicode-bidi v0.3.13 +│ │ │ └── unicode-normalization v0.1.22 +│ │ │ └── tinyvec v1.6.0 +│ │ │ └── tinyvec_macros v0.1.1 +│ │ ├── percent-encoding v2.3.0 +│ │ └── serde v1.0.190 (*) +│ └── uuid v1.5.0 +│ └── serde v1.0.190 (*) +│ [build-dependencies] +│ ├── glob v0.3.1 +│ ├── lazy_static v1.4.0 +│ └── serde_json v1.0.108 +│ ├── itoa v1.0.9 +│ ├── ryu v1.0.15 +│ └── serde v1.0.190 (*) +├── frame-support v24.0.0 +│ ├── aquamarine v0.3.2 (proc-macro) +│ │ ├── include_dir v0.7.3 +│ │ │ └── include_dir_macros v0.7.3 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ └── quote v1.0.33 (*) +│ │ ├── itertools v0.10.5 +│ │ │ └── either v1.9.0 +│ │ ├── proc-macro-error v1.0.4 +│ │ │ ├── proc-macro-error-attr v1.0.4 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ └── quote v1.0.33 (*) +│ │ │ │ [build-dependencies] +│ │ │ │ └── version_check v0.9.4 +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ │ [build-dependencies] +│ │ │ └── version_check v0.9.4 +│ │ ├── proc-macro2 v1.0.69 (*) +│ │ ├── quote v1.0.33 (*) +│ │ └── syn v1.0.109 (*) +│ ├── bitflags v1.3.2 +│ ├── docify v0.2.6 +│ │ └── docify_macros v0.2.6 (proc-macro) +│ │ ├── common-path v1.0.0 +│ │ ├── derive-syn-parse v0.1.5 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ ├── once_cell v1.18.0 +│ │ ├── proc-macro2 v1.0.69 (*) +│ │ ├── quote v1.0.33 (*) +│ │ ├── regex v1.10.2 +│ │ │ ├── aho-corasick v1.1.2 +│ │ │ │ └── memchr v2.6.4 +│ │ │ ├── memchr v2.6.4 +│ │ │ ├── regex-automata v0.4.3 +│ │ │ │ ├── aho-corasick v1.1.2 (*) +│ │ │ │ ├── memchr v2.6.4 +│ │ │ │ └── regex-syntax v0.8.2 +│ │ │ └── regex-syntax v0.8.2 +│ │ ├── syn v2.0.38 (*) +│ │ ├── termcolor v1.3.0 +│ │ ├── toml v0.7.8 +│ │ │ ├── serde v1.0.190 (*) +│ │ │ ├── serde_spanned v0.6.4 +│ │ │ │ └── serde v1.0.190 (*) +│ │ │ ├── toml_datetime v0.6.5 +│ │ │ │ └── serde v1.0.190 (*) +│ │ │ └── toml_edit v0.19.15 +│ │ │ ├── indexmap v2.0.2 +│ │ │ │ ├── equivalent v1.0.1 +│ │ │ │ └── hashbrown v0.14.2 +│ │ │ ├── serde v1.0.190 (*) +│ │ │ ├── serde_spanned v0.6.4 (*) +│ │ │ ├── toml_datetime v0.6.5 (*) +│ │ │ └── winnow v0.5.17 +│ │ └── walkdir v2.4.0 +│ │ └── same-file v1.0.6 +│ ├── environmental v1.1.4 +│ ├── frame-metadata v16.0.0 +│ │ ├── cfg-if v1.0.0 +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ └── scale-info v2.10.0 (*) +│ ├── frame-support-procedural v19.0.0 (proc-macro) +│ │ ├── Inflector v0.11.4 +│ │ │ ├── lazy_static v1.4.0 +│ │ │ └── regex v1.10.2 (*) +│ │ ├── cfg-expr v0.15.5 +│ │ │ └── smallvec v1.11.1 +│ │ ├── derive-syn-parse v0.1.5 (proc-macro) (*) +│ │ ├── expander v2.0.0 +│ │ │ ├── blake2 v0.10.6 +│ │ │ │ └── digest v0.10.7 +│ │ │ │ ├── block-buffer v0.10.4 +│ │ │ │ │ └── generic-array v0.14.7 +│ │ │ │ │ └── typenum v1.17.0 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── version_check v0.9.4 +│ │ │ │ ├── crypto-common v0.1.6 +│ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ └── typenum v1.17.0 +│ │ │ │ └── subtle v2.4.1 +│ │ │ ├── fs-err v2.9.0 +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v2.0.38 (*) +│ │ ├── frame-support-procedural-tools v8.0.0 +│ │ │ ├── frame-support-procedural-tools-derive v9.0.0 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v2.0.38 (*) +│ │ ├── itertools v0.10.5 (*) +│ │ ├── macro_magic v0.4.2 +│ │ │ ├── macro_magic_core v0.4.2 +│ │ │ │ ├── const-random v0.1.16 +│ │ │ │ │ └── const-random-macro v0.1.16 (proc-macro) +│ │ │ │ │ ├── getrandom v0.2.10 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ └── tiny-keccak v2.0.2 +│ │ │ │ │ └── crunchy v0.2.2 +│ │ │ │ ├── derive-syn-parse v0.1.5 (proc-macro) (*) +│ │ │ │ ├── macro_magic_core_macros v0.4.3 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ ├── macro_magic_macros v0.4.2 (proc-macro) +│ │ │ │ ├── macro_magic_core v0.4.2 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v2.0.38 (*) +│ │ ├── proc-macro-warning v0.4.2 +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v2.0.38 (*) +│ │ ├── proc-macro2 v1.0.69 (*) +│ │ ├── quote v1.0.33 (*) +│ │ └── syn v2.0.38 (*) +│ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ ├── k256 v0.13.1 +│ │ ├── cfg-if v1.0.0 +│ │ ├── ecdsa v0.16.8 +│ │ │ ├── der v0.7.8 +│ │ │ │ ├── const-oid v0.9.5 +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── digest v0.10.7 (*) +│ │ │ ├── elliptic-curve v0.13.6 +│ │ │ │ ├── base16ct v0.2.0 +│ │ │ │ ├── crypto-bigint v0.5.3 +│ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ ├── ff v0.13.0 +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ ├── group v0.13.0 +│ │ │ │ │ ├── ff v0.13.0 (*) +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ ├── sec1 v0.7.3 +│ │ │ │ │ ├── base16ct v0.2.0 +│ │ │ │ │ ├── der v0.7.8 (*) +│ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── subtle v2.4.1 +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── rfc6979 v0.4.0 +│ │ │ │ ├── hmac v0.12.1 (*) +│ │ │ │ └── subtle v2.4.1 +│ │ │ └── signature v2.1.0 +│ │ │ ├── digest v0.10.7 (*) +│ │ │ └── rand_core v0.6.4 (*) +│ │ ├── elliptic-curve v0.13.6 (*) +│ │ └── sha2 v0.10.8 (*) +│ ├── log v0.4.20 +│ ├── macro_magic v0.4.2 +│ │ └── macro_magic_macros v0.4.2 (proc-macro) (*) +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── paste v1.0.14 (proc-macro) +│ ├── scale-info v2.10.0 (*) +│ ├── serde v1.0.190 (*) +│ ├── serde_json v1.0.108 (*) +│ ├── smallvec v1.11.1 +│ ├── sp-api v22.0.0 +│ │ ├── log v0.4.20 +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── sp-api-proc-macro v11.0.0 (proc-macro) +│ │ │ ├── Inflector v0.11.4 (*) +│ │ │ ├── blake2 v0.10.6 (*) +│ │ │ ├── expander v2.0.0 (*) +│ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v2.0.38 (*) +│ │ ├── sp-core v24.0.0 +│ │ │ ├── array-bytes v6.1.0 +│ │ │ ├── bitflags v1.3.2 +│ │ │ ├── blake2 v0.10.6 +│ │ │ │ └── digest v0.10.7 (*) +│ │ │ ├── bounded-collections v0.1.9 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ └── serde v1.0.190 (*) +│ │ │ ├── bs58 v0.5.0 +│ │ │ ├── dyn-clonable v0.9.0 +│ │ │ │ ├── dyn-clonable-impl v0.9.0 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ └── dyn-clone v1.0.14 +│ │ │ ├── ed25519-zebra v3.1.0 +│ │ │ │ ├── curve25519-dalek v3.2.0 +│ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ ├── digest v0.9.0 +│ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ ├── rand_core v0.5.1 +│ │ │ │ │ │ └── getrandom v0.1.16 +│ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── hashbrown v0.12.3 +│ │ │ │ │ └── ahash v0.7.7 +│ │ │ │ │ └── once_cell v1.18.0 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── version_check v0.9.4 +│ │ │ │ ├── hex v0.4.3 +│ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ ├── sha2 v0.9.9 +│ │ │ │ │ ├── block-buffer v0.9.0 +│ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── digest v0.9.0 (*) +│ │ │ │ │ └── opaque-debug v0.3.0 +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── futures v0.3.29 (*) +│ │ │ ├── hash-db v0.16.0 +│ │ │ ├── hash256-std-hasher v0.15.2 +│ │ │ │ └── crunchy v0.2.2 +│ │ │ ├── impl-serde v0.4.0 +│ │ │ │ └── serde v1.0.190 (*) +│ │ │ ├── lazy_static v1.4.0 +│ │ │ ├── libsecp256k1 v0.7.1 +│ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ ├── base64 v0.13.1 +│ │ │ │ ├── digest v0.9.0 (*) +│ │ │ │ ├── hmac-drbg v0.3.0 +│ │ │ │ │ ├── digest v0.9.0 (*) +│ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ └── hmac v0.8.1 +│ │ │ │ │ ├── crypto-mac v0.8.0 +│ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ └── digest v0.9.0 (*) +│ │ │ │ ├── libsecp256k1-core v0.3.0 +│ │ │ │ │ ├── crunchy v0.2.2 +│ │ │ │ │ ├── digest v0.9.0 (*) +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ ├── rand v0.8.5 +│ │ │ │ │ ├── rand_chacha v0.3.1 +│ │ │ │ │ │ ├── ppv-lite86 v0.2.17 +│ │ │ │ │ │ └── rand_core v0.6.4 (*) +│ │ │ │ │ └── rand_core v0.6.4 (*) +│ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ ├── sha2 v0.9.9 (*) +│ │ │ │ └── typenum v1.17.0 +│ │ │ │ [build-dependencies] +│ │ │ │ ├── libsecp256k1-gen-ecmult v0.3.0 +│ │ │ │ │ └── libsecp256k1-core v0.3.0 +│ │ │ │ │ ├── crunchy v0.2.2 +│ │ │ │ │ ├── digest v0.9.0 +│ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ └── libsecp256k1-gen-genmult v0.3.0 +│ │ │ │ └── libsecp256k1-core v0.3.0 (*) +│ │ │ ├── log v0.4.20 +│ │ │ ├── merlin v2.0.1 +│ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ ├── keccak v0.1.4 +│ │ │ │ ├── rand_core v0.5.1 (*) +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── parking_lot v0.12.1 +│ │ │ │ ├── lock_api v0.4.11 +│ │ │ │ │ └── scopeguard v1.2.0 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── autocfg v1.1.0 +│ │ │ │ └── parking_lot_core v0.9.9 +│ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ └── smallvec v1.11.1 +│ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ ├── primitive-types v0.12.2 +│ │ │ │ ├── fixed-hash v0.8.0 +│ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── rustc-hex v2.1.0 +│ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ ├── impl-codec v0.6.0 +│ │ │ │ │ └── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── impl-serde v0.4.0 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ └── uint v0.9.5 +│ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ ├── crunchy v0.2.2 +│ │ │ │ ├── hex v0.4.3 +│ │ │ │ └── static_assertions v1.1.0 +│ │ │ ├── rand v0.8.5 (*) +│ │ │ ├── regex v1.10.2 +│ │ │ │ ├── aho-corasick v1.1.2 +│ │ │ │ │ └── memchr v2.6.4 +│ │ │ │ ├── memchr v2.6.4 +│ │ │ │ ├── regex-automata v0.4.3 +│ │ │ │ │ ├── aho-corasick v1.1.2 (*) +│ │ │ │ │ ├── memchr v2.6.4 +│ │ │ │ │ └── regex-syntax v0.8.2 +│ │ │ │ └── regex-syntax v0.8.2 +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ ├── schnorrkel v0.9.1 +│ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ ├── arrayvec v0.5.2 +│ │ │ │ ├── curve25519-dalek v2.1.3 +│ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ ├── digest v0.8.1 +│ │ │ │ │ │ └── generic-array v0.12.4 +│ │ │ │ │ │ └── typenum v1.17.0 +│ │ │ │ │ ├── rand_core v0.5.1 (*) +│ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── getrandom v0.1.16 (*) +│ │ │ │ ├── merlin v2.0.1 (*) +│ │ │ │ ├── rand v0.7.3 +│ │ │ │ │ ├── getrandom v0.1.16 (*) +│ │ │ │ │ ├── rand_chacha v0.2.2 +│ │ │ │ │ │ ├── ppv-lite86 v0.2.17 +│ │ │ │ │ │ └── rand_core v0.5.1 (*) +│ │ │ │ │ └── rand_core v0.5.1 (*) +│ │ │ │ ├── rand_core v0.5.1 (*) +│ │ │ │ ├── sha2 v0.8.2 +│ │ │ │ │ ├── block-buffer v0.7.3 +│ │ │ │ │ │ ├── block-padding v0.1.5 +│ │ │ │ │ │ │ └── byte-tools v0.3.1 +│ │ │ │ │ │ ├── byte-tools v0.3.1 +│ │ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ │ └── generic-array v0.12.4 (*) +│ │ │ │ │ ├── digest v0.8.1 (*) +│ │ │ │ │ ├── fake-simd v0.1.2 +│ │ │ │ │ └── opaque-debug v0.2.3 +│ │ │ │ ├── subtle v2.4.1 +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── secp256k1 v0.24.3 +│ │ │ │ └── secp256k1-sys v0.6.1 +│ │ │ │ [build-dependencies] +│ │ │ │ └── cc v1.0.83 +│ │ │ │ └── libc v0.2.149 +│ │ │ ├── secrecy v0.8.0 +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── serde v1.0.190 (*) +│ │ │ ├── sp-core-hashing v12.0.0 +│ │ │ │ ├── blake2b_simd v1.0.2 +│ │ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ │ └── constant_time_eq v0.3.0 +│ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ ├── sha3 v0.10.8 +│ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ └── keccak v0.1.4 +│ │ │ │ └── twox-hash v1.6.3 +│ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ └── static_assertions v1.1.0 +│ │ │ ├── sp-debug-derive v11.0.0 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ ├── sp-externalities v0.22.0 +│ │ │ │ ├── environmental v1.1.4 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ └── sp-storage v16.0.0 +│ │ │ │ ├── impl-serde v0.4.0 (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── ref-cast v1.0.20 +│ │ │ │ │ └── ref-cast-impl v1.0.20 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ ├── sp-debug-derive v11.0.0 (proc-macro) (*) +│ │ │ │ └── sp-std v11.0.0 +│ │ │ ├── sp-runtime-interface v20.0.0 +│ │ │ │ ├── bytes v1.5.0 +│ │ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── primitive-types v0.12.2 (*) +│ │ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ │ ├── sp-runtime-interface-proc-macro v14.0.0 (proc-macro) +│ │ │ │ │ ├── Inflector v0.11.4 (*) +│ │ │ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ ├── sp-storage v16.0.0 (*) +│ │ │ │ ├── sp-tracing v13.0.0 +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ ├── tracing-core v0.1.32 (*) +│ │ │ │ │ └── tracing-subscriber v0.2.25 +│ │ │ │ │ ├── ansi_term v0.12.1 +│ │ │ │ │ ├── chrono v0.4.31 (*) +│ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ ├── matchers v0.0.1 +│ │ │ │ │ │ └── regex-automata v0.1.10 +│ │ │ │ │ │ └── regex-syntax v0.6.29 +│ │ │ │ │ ├── regex v1.10.2 (*) +│ │ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ │ ├── serde_json v1.0.108 (*) +│ │ │ │ │ ├── sharded-slab v0.1.7 +│ │ │ │ │ │ └── lazy_static v1.4.0 +│ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ ├── thread_local v1.1.7 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ └── once_cell v1.18.0 +│ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ ├── tracing-core v0.1.32 (*) +│ │ │ │ │ ├── tracing-log v0.1.4 +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ │ └── tracing-core v0.1.32 (*) +│ │ │ │ │ └── tracing-serde v0.1.3 +│ │ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ │ └── tracing-core v0.1.32 (*) +│ │ │ │ ├── sp-wasm-interface v17.0.0 +│ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ └── wasmtime v8.0.1 +│ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ ├── bincode v1.3.3 +│ │ │ │ │ │ └── serde v1.0.190 (*) +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── indexmap v1.9.3 +│ │ │ │ │ │ ├── hashbrown v0.12.3 (*) +│ │ │ │ │ │ └── serde v1.0.190 (*) +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── autocfg v1.1.0 +│ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── object v0.30.4 +│ │ │ │ │ │ ├── crc32fast v1.3.2 +│ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ ├── hashbrown v0.13.2 +│ │ │ │ │ │ │ └── ahash v0.8.6 +│ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ ├── getrandom v0.2.10 (*) +│ │ │ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ │ │ └── zerocopy v0.7.20 +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── version_check v0.9.4 +│ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ └── memchr v2.6.4 +│ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ │ │ ├── psm v0.1.21 +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ ├── wasmparser v0.102.0 +│ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ └── url v2.4.1 (*) +│ │ │ │ │ ├── wasmtime-environ v8.0.1 +│ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ ├── cranelift-entity v0.95.1 +│ │ │ │ │ │ │ └── serde v1.0.190 (*) +│ │ │ │ │ │ ├── gimli v0.27.3 +│ │ │ │ │ │ │ ├── fallible-iterator v0.2.0 +│ │ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ │ └── stable_deref_trait v1.2.0 +│ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ ├── wasmparser v0.102.0 (*) +│ │ │ │ │ │ └── wasmtime-types v8.0.1 +│ │ │ │ │ │ ├── cranelift-entity v0.95.1 (*) +│ │ │ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ └── wasmparser v0.102.0 (*) +│ │ │ │ │ ├── wasmtime-jit v8.0.1 +│ │ │ │ │ │ ├── addr2line v0.19.0 +│ │ │ │ │ │ │ └── gimli v0.27.3 (*) +│ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ ├── bincode v1.3.3 (*) +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ ├── cpp_demangle v0.3.5 +│ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ ├── gimli v0.27.3 (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ │ ├── rustc-demangle v0.1.23 +│ │ │ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ │ ├── wasmtime-environ v8.0.1 (*) +│ │ │ │ │ │ ├── wasmtime-jit-icache-coherence v8.0.1 +│ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ └── wasmtime-runtime v8.0.1 +│ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── memfd v0.6.4 +│ │ │ │ │ │ │ └── rustix v0.38.21 +│ │ │ │ │ │ │ ├── bitflags v2.4.1 +│ │ │ │ │ │ │ ├── errno v0.3.5 +│ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ ├── memoffset v0.8.0 +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── autocfg v1.1.0 +│ │ │ │ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ ├── wasmtime-asm-macros v8.0.1 +│ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ ├── wasmtime-environ v8.0.1 (*) +│ │ │ │ │ │ └── wasmtime-jit-debug v8.0.1 +│ │ │ │ │ │ └── once_cell v1.18.0 +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ │ └── wasmtime-runtime v8.0.1 (*) +│ │ │ │ └── static_assertions v1.1.0 +│ │ │ ├── sp-std v11.0.0 +│ │ │ ├── sp-storage v16.0.0 (*) +│ │ │ ├── ss58-registry v1.43.0 +│ │ │ │ └── num-format v0.4.4 +│ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ └── itoa v1.0.9 +│ │ │ │ [build-dependencies] +│ │ │ │ ├── Inflector v0.11.4 (*) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ ├── serde_json v1.0.108 (*) +│ │ │ │ └── unicode-xid v0.2.4 +│ │ │ ├── substrate-bip39 v0.4.5 +│ │ │ │ ├── hmac v0.11.0 +│ │ │ │ │ ├── crypto-mac v0.11.1 +│ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ └── digest v0.9.0 (*) +│ │ │ │ ├── pbkdf2 v0.8.0 +│ │ │ │ │ └── crypto-mac v0.11.1 (*) +│ │ │ │ ├── schnorrkel v0.9.1 (*) +│ │ │ │ ├── sha2 v0.9.9 (*) +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── thiserror v1.0.50 (*) +│ │ │ ├── tiny-bip39 v1.0.0 +│ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ ├── hmac v0.12.1 (*) +│ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ ├── pbkdf2 v0.11.0 +│ │ │ │ │ └── digest v0.10.7 (*) +│ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ ├── rustc-hash v1.1.0 +│ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ ├── unicode-normalization v0.1.22 (*) +│ │ │ │ ├── wasm-bindgen v0.2.87 (*) +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── tracing v0.1.40 (*) +│ │ │ └── zeroize v1.6.0 (*) +│ │ ├── sp-metadata-ir v0.3.0 +│ │ │ ├── frame-metadata v16.0.0 (*) +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ └── sp-std v11.0.0 +│ │ ├── sp-runtime v27.0.0 +│ │ │ ├── either v1.9.0 +│ │ │ ├── hash256-std-hasher v0.15.2 (*) +│ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ ├── log v0.4.20 +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ ├── rand v0.8.5 (*) +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ ├── serde v1.0.190 (*) +│ │ │ ├── sp-application-crypto v26.0.0 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-io v26.0.0 +│ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ ├── ed25519-dalek v2.0.0 +│ │ │ │ │ │ ├── curve25519-dalek v4.1.1 +│ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ ├── platforms v3.1.2 +│ │ │ │ │ │ │ └── rustc_version v0.4.0 +│ │ │ │ │ │ │ └── semver v1.0.20 +│ │ │ │ │ │ ├── ed25519 v2.2.3 +│ │ │ │ │ │ │ └── signature v2.1.0 (*) +│ │ │ │ │ │ └── sha2 v0.10.8 (*) +│ │ │ │ │ ├── libsecp256k1 v0.7.1 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── secp256k1 v0.24.3 (*) +│ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ │ │ ├── sp-keystore v0.30.0 +│ │ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ │ ├── sp-runtime-interface v20.0.0 (*) +│ │ │ │ │ ├── sp-state-machine v0.31.0 +│ │ │ │ │ │ ├── hash-db v0.16.0 +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ │ │ │ ├── sp-panic-handler v11.0.0 +│ │ │ │ │ │ │ ├── backtrace v0.3.69 +│ │ │ │ │ │ │ │ ├── addr2line v0.21.0 +│ │ │ │ │ │ │ │ │ └── gimli v0.28.0 +│ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ │ │ ├── miniz_oxide v0.7.1 +│ │ │ │ │ │ │ │ │ └── adler v1.0.2 +│ │ │ │ │ │ │ │ ├── object v0.32.1 +│ │ │ │ │ │ │ │ │ └── memchr v2.6.4 +│ │ │ │ │ │ │ │ └── rustc-demangle v0.1.23 +│ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ │ │ └── regex v1.10.2 (*) +│ │ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ │ ├── sp-trie v25.0.0 +│ │ │ │ │ │ │ ├── ahash v0.8.6 (*) +│ │ │ │ │ │ │ ├── hash-db v0.16.0 +│ │ │ │ │ │ │ ├── hashbrown v0.13.2 (*) +│ │ │ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ │ │ ├── memory-db v0.32.0 +│ │ │ │ │ │ │ │ └── hash-db v0.16.0 +│ │ │ │ │ │ │ ├── nohash-hasher v0.2.0 +│ │ │ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ │ │ │ ├── schnellru v0.2.1 +│ │ │ │ │ │ │ │ ├── ahash v0.8.6 (*) +│ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ └── hashbrown v0.13.2 (*) +│ │ │ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ │ │ ├── trie-db v0.28.0 +│ │ │ │ │ │ │ │ ├── hash-db v0.16.0 +│ │ │ │ │ │ │ │ ├── hashbrown v0.13.2 (*) +│ │ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ │ ├── rustc-hex v2.1.0 +│ │ │ │ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ │ │ │ └── trie-root v0.18.0 +│ │ │ │ │ │ │ └── hash-db v0.16.0 +│ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ │ └── trie-db v0.28.0 (*) +│ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ ├── sp-tracing v13.0.0 (*) +│ │ │ │ │ ├── sp-trie v25.0.0 (*) +│ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ └── tracing-core v0.1.32 (*) +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── rustversion v1.0.14 (proc-macro) +│ │ │ │ └── sp-std v11.0.0 +│ │ │ ├── sp-arithmetic v19.0.0 +│ │ │ │ ├── integer-sqrt v0.1.5 +│ │ │ │ │ └── num-traits v0.2.17 (*) +│ │ │ │ ├── num-traits v0.2.17 (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ └── static_assertions v1.1.0 +│ │ │ ├── sp-core v24.0.0 (*) +│ │ │ ├── sp-io v26.0.0 (*) +│ │ │ ├── sp-std v11.0.0 +│ │ │ └── sp-weights v23.0.0 +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ ├── serde v1.0.190 (*) +│ │ │ ├── smallvec v1.11.1 +│ │ │ ├── sp-arithmetic v19.0.0 (*) +│ │ │ ├── sp-core v24.0.0 (*) +│ │ │ ├── sp-debug-derive v11.0.0 (proc-macro) (*) +│ │ │ └── sp-std v11.0.0 +│ │ ├── sp-std v11.0.0 +│ │ └── sp-version v25.0.0 +│ │ ├── impl-serde v0.4.0 (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── serde v1.0.190 (*) +│ │ ├── sp-core-hashing-proc-macro v12.0.0 (proc-macro) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ ├── sp-core-hashing v12.0.0 +│ │ │ │ ├── blake2b_simd v1.0.2 +│ │ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ │ └── constant_time_eq v0.3.0 +│ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ ├── sha2 v0.10.8 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── cpufeatures v0.2.11 +│ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ └── digest v0.10.7 (*) +│ │ │ │ ├── sha3 v0.10.8 +│ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ └── keccak v0.1.4 +│ │ │ │ │ └── cpufeatures v0.2.11 (*) +│ │ │ │ └── twox-hash v1.6.3 +│ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ └── static_assertions v1.1.0 +│ │ │ └── syn v2.0.38 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ ├── sp-std v11.0.0 +│ │ └── sp-version-proc-macro v11.0.0 (proc-macro) +│ │ ├── parity-scale-codec v3.6.5 +│ │ │ ├── arrayvec v0.7.4 +│ │ │ ├── byte-slice-cast v1.2.2 +│ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ ├── parity-scale-codec-derive v3.6.5 (proc-macro) (*) +│ │ │ └── serde v1.0.190 (*) +│ │ ├── proc-macro2 v1.0.69 (*) +│ │ ├── quote v1.0.33 (*) +│ │ └── syn v2.0.38 (*) +│ ├── sp-arithmetic v19.0.0 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-core-hashing-proc-macro v12.0.0 (proc-macro) (*) +│ ├── sp-debug-derive v11.0.0 (proc-macro) (*) +│ ├── sp-genesis-builder v0.3.0 +│ │ ├── serde_json v1.0.108 (*) +│ │ ├── sp-api v22.0.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ └── sp-std v11.0.0 +│ ├── sp-inherents v22.0.0 +│ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ └── sp-std v11.0.0 +│ ├── sp-io v26.0.0 (*) +│ ├── sp-metadata-ir v0.3.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ ├── sp-staking v22.0.0 +│ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── sp-core v24.0.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ └── sp-std v11.0.0 +│ ├── sp-std v11.0.0 +│ ├── sp-tracing v13.0.0 (*) +│ ├── sp-weights v23.0.0 (*) +│ ├── static_assertions v1.1.0 +│ └── tt-call v1.0.9 +├── frame-system v24.0.0 +│ ├── cfg-if v1.0.0 +│ ├── frame-support v24.0.0 (*) +│ ├── log v0.4.20 +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── scale-info v2.10.0 (*) +│ ├── serde v1.0.190 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-io v26.0.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ ├── sp-std v11.0.0 +│ ├── sp-version v25.0.0 (*) +│ └── sp-weights v23.0.0 (*) +├── macro-attr-2018 v3.0.0 +├── newtype-derive-2018 v0.2.1 (*) +├── parity-scale-codec v3.6.5 (*) +├── scale-info v2.10.0 (*) +├── sp-std v11.0.0 +├── tracing v0.1.40 (*) +└── uuid v1.5.0 (*) +[dev-dependencies] +├── chronicle-telemetry v0.7.5 (/Users/ryan/code/chronicle/crates/chronicle-telemetry) +│ ├── cfg-if v1.0.0 +│ ├── console-subscriber v0.1.10 +│ │ ├── console-api v0.5.0 +│ │ │ ├── prost v0.11.9 +│ │ │ │ ├── bytes v1.5.0 +│ │ │ │ └── prost-derive v0.11.9 (proc-macro) +│ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ ├── itertools v0.10.5 (*) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v1.0.109 (*) +│ │ │ ├── prost-types v0.11.9 +│ │ │ │ └── prost v0.11.9 (*) +│ │ │ ├── tonic v0.9.2 +│ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ ├── axum v0.6.20 +│ │ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ │ ├── axum-core v0.3.4 +│ │ │ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ ├── futures-util v0.3.29 (*) +│ │ │ │ │ │ ├── http v0.2.9 +│ │ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ │ │ └── itoa v1.0.9 +│ │ │ │ │ │ ├── http-body v0.4.5 +│ │ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ │ │ │ └── pin-project-lite v0.2.13 +│ │ │ │ │ │ ├── mime v0.3.17 +│ │ │ │ │ │ ├── tower-layer v0.3.2 +│ │ │ │ │ │ └── tower-service v0.3.2 +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── rustversion v1.0.14 (proc-macro) +│ │ │ │ │ ├── bitflags v1.3.2 +│ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ ├── futures-util v0.3.29 (*) +│ │ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ │ ├── http-body v0.4.5 (*) +│ │ │ │ │ ├── hyper v0.14.27 +│ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ ├── futures-channel v0.3.29 (*) +│ │ │ │ │ │ ├── futures-core v0.3.29 +│ │ │ │ │ │ ├── futures-util v0.3.29 (*) +│ │ │ │ │ │ ├── h2 v0.3.21 +│ │ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ │ │ ├── futures-core v0.3.29 +│ │ │ │ │ │ │ ├── futures-sink v0.3.29 +│ │ │ │ │ │ │ ├── futures-util v0.3.29 (*) +│ │ │ │ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ │ ├── slab v0.4.9 (*) +│ │ │ │ │ │ │ ├── tokio v1.33.0 +│ │ │ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ │ │ ├── mio v0.8.9 +│ │ │ │ │ │ │ │ ├── num_cpus v1.16.0 (*) +│ │ │ │ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ │ │ │ └── tokio-macros v2.1.0 (proc-macro) +│ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ │ │ ├── tokio-util v0.7.10 +│ │ │ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ │ │ ├── futures-core v0.3.29 +│ │ │ │ │ │ │ │ ├── futures-sink v0.3.29 +│ │ │ │ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ │ │ │ └── tracing v0.1.40 (*) +│ │ │ │ │ │ │ └── tracing v0.1.40 (*) +│ │ │ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ │ │ ├── http-body v0.4.5 (*) +│ │ │ │ │ │ ├── httparse v1.8.0 +│ │ │ │ │ │ ├── httpdate v1.0.3 +│ │ │ │ │ │ ├── itoa v1.0.9 +│ │ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ │ ├── socket2 v0.4.10 +│ │ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ │ ├── tower-service v0.3.2 +│ │ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ │ └── want v0.3.1 +│ │ │ │ │ │ └── try-lock v0.2.4 +│ │ │ │ │ ├── itoa v1.0.9 +│ │ │ │ │ ├── matchit v0.7.3 +│ │ │ │ │ ├── memchr v2.6.4 +│ │ │ │ │ ├── mime v0.3.17 +│ │ │ │ │ ├── percent-encoding v2.3.0 +│ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ ├── serde v1.0.190 (*) +│ │ │ │ │ ├── sync_wrapper v0.1.2 +│ │ │ │ │ ├── tower v0.4.13 +│ │ │ │ │ │ ├── futures-core v0.3.29 +│ │ │ │ │ │ ├── futures-util v0.3.29 (*) +│ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ ├── pin-project v1.1.3 +│ │ │ │ │ │ │ └── pin-project-internal v1.1.3 (proc-macro) +│ │ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ ├── slab v0.4.9 (*) +│ │ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ │ ├── tokio-util v0.7.10 (*) +│ │ │ │ │ │ ├── tower-layer v0.3.2 +│ │ │ │ │ │ ├── tower-service v0.3.2 +│ │ │ │ │ │ └── tracing v0.1.40 (*) +│ │ │ │ │ ├── tower-layer v0.3.2 +│ │ │ │ │ └── tower-service v0.3.2 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── rustversion v1.0.14 (proc-macro) +│ │ │ │ ├── base64 v0.21.5 +│ │ │ │ ├── bytes v1.5.0 +│ │ │ │ ├── futures-core v0.3.29 +│ │ │ │ ├── futures-util v0.3.29 (*) +│ │ │ │ ├── h2 v0.3.21 (*) +│ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ ├── http-body v0.4.5 (*) +│ │ │ │ ├── hyper v0.14.27 (*) +│ │ │ │ ├── hyper-timeout v0.4.1 +│ │ │ │ │ ├── hyper v0.14.27 (*) +│ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ └── tokio-io-timeout v1.2.0 +│ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ └── tokio v1.33.0 (*) +│ │ │ │ ├── percent-encoding v2.3.0 +│ │ │ │ ├── pin-project v1.1.3 (*) +│ │ │ │ ├── prost v0.11.9 (*) +│ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ ├── tokio-stream v0.1.14 +│ │ │ │ │ ├── futures-core v0.3.29 +│ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ └── tokio v1.33.0 (*) +│ │ │ │ ├── tower v0.4.13 (*) +│ │ │ │ ├── tower-layer v0.3.2 +│ │ │ │ ├── tower-service v0.3.2 +│ │ │ │ └── tracing v0.1.40 (*) +│ │ │ └── tracing-core v0.1.32 (*) +│ │ ├── crossbeam-channel v0.5.8 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ └── crossbeam-utils v0.8.16 +│ │ │ └── cfg-if v1.0.0 +│ │ ├── crossbeam-utils v0.8.16 (*) +│ │ ├── futures v0.3.29 (*) +│ │ ├── hdrhistogram v7.5.2 +│ │ │ ├── base64 v0.13.1 +│ │ │ ├── byteorder v1.5.0 +│ │ │ ├── flate2 v1.0.28 +│ │ │ │ ├── crc32fast v1.3.2 (*) +│ │ │ │ └── miniz_oxide v0.7.1 (*) +│ │ │ ├── nom v7.1.3 +│ │ │ │ ├── memchr v2.6.4 +│ │ │ │ └── minimal-lexical v0.2.1 +│ │ │ └── num-traits v0.2.17 (*) +│ │ ├── humantime v2.1.0 +│ │ ├── prost-types v0.11.9 (*) +│ │ ├── serde v1.0.190 (*) +│ │ ├── serde_json v1.0.108 (*) +│ │ ├── thread_local v1.1.7 (*) +│ │ ├── tokio v1.33.0 (*) +│ │ ├── tokio-stream v0.1.14 (*) +│ │ ├── tonic v0.9.2 (*) +│ │ ├── tracing v0.1.40 (*) +│ │ ├── tracing-core v0.1.32 (*) +│ │ └── tracing-subscriber v0.3.17 +│ │ ├── matchers v0.1.0 +│ │ │ └── regex-automata v0.1.10 (*) +│ │ ├── nu-ansi-term v0.46.0 +│ │ │ └── overload v0.1.1 +│ │ ├── once_cell v1.18.0 +│ │ ├── regex v1.10.2 (*) +│ │ ├── serde v1.0.190 (*) +│ │ ├── serde_json v1.0.108 (*) +│ │ ├── sharded-slab v0.1.7 (*) +│ │ ├── smallvec v1.11.1 +│ │ ├── thread_local v1.1.7 (*) +│ │ ├── tracing v0.1.40 (*) +│ │ ├── tracing-core v0.1.32 (*) +│ │ ├── tracing-log v0.1.4 (*) +│ │ └── tracing-serde v0.1.3 (*) +│ ├── tracing v0.1.40 (*) +│ ├── tracing-elastic-apm v3.2.3 +│ │ ├── anyhow v1.0.75 +│ │ ├── base64 v0.13.1 +│ │ ├── fxhash v0.2.1 +│ │ │ └── byteorder v1.5.0 +│ │ ├── rand v0.8.5 (*) +│ │ ├── reqwest v0.11.22 +│ │ │ ├── base64 v0.21.5 +│ │ │ ├── bytes v1.5.0 +│ │ │ ├── futures-core v0.3.29 +│ │ │ ├── futures-util v0.3.29 (*) +│ │ │ ├── http v0.2.9 (*) +│ │ │ ├── js-sys v0.3.64 (*) +│ │ │ ├── serde v1.0.190 (*) +│ │ │ ├── serde_json v1.0.108 (*) +│ │ │ ├── serde_urlencoded v0.7.1 +│ │ │ │ ├── form_urlencoded v1.2.0 (*) +│ │ │ │ ├── itoa v1.0.9 +│ │ │ │ ├── ryu v1.0.15 +│ │ │ │ └── serde v1.0.190 (*) +│ │ │ ├── tower-service v0.3.2 +│ │ │ ├── url v2.4.1 (*) +│ │ │ ├── wasm-bindgen v0.2.87 (*) +│ │ │ ├── wasm-bindgen-futures v0.4.37 +│ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ ├── js-sys v0.3.64 (*) +│ │ │ │ └── wasm-bindgen v0.2.87 (*) +│ │ │ └── web-sys v0.3.64 +│ │ │ ├── js-sys v0.3.64 (*) +│ │ │ └── wasm-bindgen v0.2.87 (*) +│ │ ├── serde v1.0.190 (*) +│ │ ├── serde_json v1.0.108 (*) +│ │ ├── tokio v1.33.0 (*) +│ │ ├── tracing v0.1.40 (*) +│ │ ├── tracing-subscriber v0.3.17 (*) +│ │ └── version v3.0.0 +│ ├── tracing-log v0.1.4 (*) +│ ├── tracing-subscriber v0.3.17 (*) +│ └── url v2.4.1 (*) +├── sp-core v24.0.0 (*) +└── sp-runtime v27.0.0 (*) diff --git a/crates/sawtooth-tp/build.rs b/crates/sawtooth-tp/build.rs index 5a1d86bbe..afb2c9546 100644 --- a/crates/sawtooth-tp/build.rs +++ b/crates/sawtooth-tp/build.rs @@ -1,8 +1,8 @@ fn main() { - //Create a .VERSION file containing 'local' if it does not exist + //Create a .VERSION file containing 'local' if it does not exist - let version_file = std::path::Path::new("../../.VERSION"); - if !version_file.exists() { - std::fs::write(version_file, "local").expect("Unable to write file"); - } + let version_file = std::path::Path::new("../../.VERSION"); + if !version_file.exists() { + std::fs::write(version_file, "local").expect("Unable to write file"); + } } diff --git a/crates/sawtooth-tp/src/abstract_tp.rs b/crates/sawtooth-tp/src/abstract_tp.rs index eb801a036..076921ca7 100644 --- a/crates/sawtooth-tp/src/abstract_tp.rs +++ b/crates/sawtooth-tp/src/abstract_tp.rs @@ -1,13 +1,13 @@ use chronicle_protocol::{address::SawtoothAddress, protocol::messages::Submission}; use common::{ - identity::SignedIdentity, - ledger::OperationState, - opa::ExecutorContext, - prov::{operations::ChronicleOperation, ChronicleTransaction}, + identity::SignedIdentity, + ledger::OperationState, + opa::ExecutorContext, + prov::{operations::ChronicleOperation, ChronicleTransaction}, }; use sawtooth_sdk::{ - messages::processor::TpProcessRequest, - processor::handler::{ApplyError, ContextError, TransactionContext}, + messages::processor::TpProcessRequest, + processor::handler::{ApplyError, ContextError, TransactionContext}, }; use tracing::instrument; // Sawtooth's &mut dyn TransactionContext is highly inconvenient to work with in @@ -16,103 +16,85 @@ use tracing::instrument; // and async part which returns effects #[async_trait::async_trait] pub trait TP { - fn tp_parse(request: &TpProcessRequest) -> Result; - fn tp_state( - context: &mut dyn TransactionContext, - operations: &ChronicleTransaction, - ) -> Result, ApplyError>; - async fn tp_operations(request: Submission) -> Result; - async fn tp( - opa_executor: ExecutorContext, - request: &TpProcessRequest, - submission: Submission, - operations: ChronicleTransaction, - state: OperationState, - ) -> Result; - async fn enforce_opa( - opa_executor: ExecutorContext, - identity: &SignedIdentity, - operation: &ChronicleOperation, - state: &OperationState, - ) -> Result<(), ApplyError>; + fn tp_parse(request: &TpProcessRequest) -> Result; + fn tp_state( + context: &mut dyn TransactionContext, + operations: &ChronicleTransaction, + ) -> Result, ApplyError>; + async fn tp_operations(request: Submission) -> Result; + async fn tp( + opa_executor: ExecutorContext, + request: &TpProcessRequest, + submission: Submission, + operations: ChronicleTransaction, + state: OperationState, + ) -> Result; + async fn enforce_opa( + opa_executor: ExecutorContext, + identity: &SignedIdentity, + operation: &ChronicleOperation, + state: &OperationState, + ) -> Result<(), ApplyError>; } #[derive(Debug)] pub enum TPSideEffect { - SetState { - address: String, - value: Vec, - }, - AddEvent { - event_type: String, - attributes: Vec<(String, String)>, - data: Vec, - }, + SetState { address: String, value: Vec }, + AddEvent { event_type: String, attributes: Vec<(String, String)>, data: Vec }, } pub struct TPSideEffects { - effects: Vec, + effects: Vec, } impl TPSideEffects { - pub fn new() -> Self { - Self { effects: vec![] } - } + pub fn new() -> Self { + Self { effects: vec![] } + } - #[instrument(name = "set_state_entry", level = "trace", skip(self, data))] - pub fn set_state_entry(&mut self, address: String, data: Vec) { - self.effects.push(TPSideEffect::SetState { - address, - value: data, - }); - } + #[instrument(name = "set_state_entry", level = "trace", skip(self, data))] + pub fn set_state_entry(&mut self, address: String, data: Vec) { + self.effects.push(TPSideEffect::SetState { address, value: data }); + } - #[instrument(name = "add_event", level = "trace", skip(self, data))] - pub fn add_event( - &mut self, - event_type: String, - attributes: Vec<(String, String)>, - data: Vec, - ) { - self.effects.push(TPSideEffect::AddEvent { - event_type, - attributes, - data, - }); - } + #[instrument(name = "add_event", level = "trace", skip(self, data))] + pub fn add_event( + &mut self, + event_type: String, + attributes: Vec<(String, String)>, + data: Vec, + ) { + self.effects.push(TPSideEffect::AddEvent { event_type, attributes, data }); + } - #[instrument(name = "apply_effects", level = "trace", skip(self, ctx), fields (effects = ?self.effects))] - pub fn apply(self, ctx: &mut dyn TransactionContext) -> Result<(), ContextError> { - for effect in self.effects.into_iter() { - match effect { - TPSideEffect::SetState { address, value } => { - ctx.set_state_entry(address.clone(), value.clone())?; - } - TPSideEffect::AddEvent { - event_type, - attributes, - data, - } => { - ctx.add_event(event_type, attributes, data.as_slice())?; - } - } - } + #[instrument(name = "apply_effects", level = "trace", skip(self, ctx), fields (effects = ?self.effects))] + pub fn apply(self, ctx: &mut dyn TransactionContext) -> Result<(), ContextError> { + for effect in self.effects.into_iter() { + match effect { + TPSideEffect::SetState { address, value } => { + ctx.set_state_entry(address.clone(), value.clone())?; + }, + TPSideEffect::AddEvent { event_type, attributes, data } => { + ctx.add_event(event_type, attributes, data.as_slice())?; + }, + } + } - Ok(()) - } + Ok(()) + } } impl Default for TPSideEffects { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } impl IntoIterator for TPSideEffects { - type IntoIter = std::vec::IntoIter; - type Item = TPSideEffect; + type IntoIter = std::vec::IntoIter; + type Item = TPSideEffect; - fn into_iter(self) -> Self::IntoIter { - self.effects.into_iter() - } + fn into_iter(self) -> Self::IntoIter { + self.effects.into_iter() + } } diff --git a/crates/sawtooth-tp/src/main.rs b/crates/sawtooth-tp/src/main.rs index d6010c1d2..fc9cb13be 100644 --- a/crates/sawtooth-tp/src/main.rs +++ b/crates/sawtooth-tp/src/main.rs @@ -11,88 +11,86 @@ use tracing::info; use url::Url; pub const LONG_VERSION: &str = const_format::formatcp!( - "{}:{}", - env!("CARGO_PKG_VERSION"), - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../.VERSION")) + "{}:{}", + env!("CARGO_PKG_VERSION"), + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../.VERSION")) ); #[tokio::main] async fn main() { - let matches = Command::new("chronicle-sawtooth-tp") - .version(LONG_VERSION) - .author("Blockchain Technology Partners") - .about("Write and query provenance data to distributed ledgers") - .arg( - Arg::new("connect") - .short('C') - .long("connect") - .value_hint(ValueHint::Url) - .help("Sets sawtooth validator address") - .takes_value(true), - ) - .arg( - Arg::new("completions") - .long("completions") - .value_name("completions") - .value_parser(PossibleValuesParser::new(["bash", "zsh", "fish"])) - .help("Generate shell completions and exit"), - ) - .arg( - Arg::new("instrument") - .long("instrument") - .value_name("instrument") - .takes_value(true) - .value_hint(ValueHint::Url) - .help("Instrument using RUST_LOG environment"), - ) - .arg( - Arg::new("console-logging") - .long("console-logging") - .value_name("console-logging") - .takes_value(true) - .default_value("pretty") - .help("Log to console using RUST_LOG environment"), - ) - .get_matches(); + let matches = Command::new("chronicle-sawtooth-tp") + .version(LONG_VERSION) + .author("Blockchain Technology Partners") + .about("Write and query provenance data to distributed ledgers") + .arg( + Arg::new("connect") + .short('C') + .long("connect") + .value_hint(ValueHint::Url) + .help("Sets sawtooth validator address") + .takes_value(true), + ) + .arg( + Arg::new("completions") + .long("completions") + .value_name("completions") + .value_parser(PossibleValuesParser::new(["bash", "zsh", "fish"])) + .help("Generate shell completions and exit"), + ) + .arg( + Arg::new("instrument") + .long("instrument") + .value_name("instrument") + .takes_value(true) + .value_hint(ValueHint::Url) + .help("Instrument using RUST_LOG environment"), + ) + .arg( + Arg::new("console-logging") + .long("console-logging") + .value_name("console-logging") + .takes_value(true) + .default_value("pretty") + .help("Log to console using RUST_LOG environment"), + ) + .get_matches(); - telemetry( - matches - .get_one::("instrument") - .and_then(|s| Url::parse(s).ok()), - match matches.get_one::("console-logging") { - Some(level) => match level.as_str() { - "pretty" => ConsoleLogging::Pretty, - "json" => ConsoleLogging::Json, - _ => ConsoleLogging::Off, - }, - _ => ConsoleLogging::Off, - }, - ); + telemetry( + matches.get_one::("instrument").and_then(|s| Url::parse(s).ok()), + match matches.get_one::("console-logging") { + Some(level) => match level.as_str() { + "pretty" => ConsoleLogging::Pretty, + "json" => ConsoleLogging::Json, + _ => ConsoleLogging::Off, + }, + _ => ConsoleLogging::Off, + }, + ); - info!(chronicle_tp_version = LONG_VERSION); + info!(chronicle_tp_version = LONG_VERSION); - let (bootstrap_policy, bootstrap_entrypoint) = - ("allow_transactions", "allow_transactions.allowed_users"); + let (bootstrap_policy, bootstrap_entrypoint) = + ("allow_transactions", "allow_transactions.allowed_users"); - Handle::current().spawn_blocking(move || { - info!( - "Starting Chronicle Transaction Processor on {:?}", - matches.get_one::("connect") - ); - let handler = match ChronicleTransactionHandler::new(bootstrap_policy, bootstrap_entrypoint) - { - Ok(handler) => handler, - Err(e) => panic!("Error initializing TransactionHandler: {e}"), - }; - let mut processor = TransactionProcessor::new({ - if let Some(connect) = matches.get_one::("connect") { - connect - } else { - "tcp://127.0.0.1:4004" - } - }); + Handle::current().spawn_blocking(move || { + info!( + "Starting Chronicle Transaction Processor on {:?}", + matches.get_one::("connect") + ); + let handler = match ChronicleTransactionHandler::new(bootstrap_policy, bootstrap_entrypoint) + { + Ok(handler) => handler, + Err(e) => panic!("Error initializing TransactionHandler: {e}"), + }; + let mut processor = TransactionProcessor::new({ + if let Some(connect) = matches.get_one::("connect") { + connect + } else { + "tcp://127.0.0.1:4004" + } + }); - processor.add_handler(&handler); - processor.start(); - }); + processor.add_handler(&handler); + processor.start(); + }); } diff --git a/crates/sawtooth-tp/src/opa.rs b/crates/sawtooth-tp/src/opa.rs index 390aac51d..0063f0807 100644 --- a/crates/sawtooth-tp/src/opa.rs +++ b/crates/sawtooth-tp/src/opa.rs @@ -2,140 +2,132 @@ use chronicle_protocol::settings::sawtooth_settings_address; use common::opa::{CliPolicyLoader, ExecutorContext}; use protobuf::Message; use sawtooth_sdk::{ - messages::setting::Setting, - processor::handler::{ApplyError, TransactionContext}, + messages::setting::Setting, + processor::handler::{ApplyError, TransactionContext}, }; use std::{ - collections::HashMap, - sync::{Arc, Mutex}, + collections::HashMap, + sync::{Arc, Mutex}, }; use tracing::{debug, info, warn}; #[derive(Debug)] pub struct TpOpa { - pub embedded: ExecutorContext, - pub on_chain: Arc>>, + pub embedded: ExecutorContext, + pub on_chain: Arc>>, } impl TpOpa { - pub fn new(policy: &str, entrypoint: &str) -> Result { - Ok(Self { - on_chain: Arc::new(HashMap::new().into()), - embedded: { - ExecutorContext::from_loader( - &CliPolicyLoader::from_embedded_policy(policy, entrypoint) - .map_err(|e| ApplyError::InternalError(e.to_string()))?, - ) - .map_err(|e| ApplyError::InternalError(e.to_string()))? - }, - }) - } - - pub fn executor_context( - &self, - ctx: &dyn TransactionContext, - ) -> Result { - let policy_name_settings_entry = - ctx.get_state_entry(&sawtooth_settings_address("chronicle.opa.policy_name"))?; - - let entrypoint_settings_entry = - ctx.get_state_entry(&sawtooth_settings_address("chronicle.opa.entrypoint"))?; - - match (policy_name_settings_entry, entrypoint_settings_entry) { - (None, None) => { - warn!("Insecure operating mode - no on-chain policy name or entrypoint settings found"); - Ok(self.embedded.clone()) - } - (Some(policy_name_settings_entry), Some(entrypoint_settings_entry)) => { - info!("Chronicle operating in secure mode - on-chain policy name and entrypoint settings found"); - let policy_name_settings_entry: Setting = - Message::parse_from_bytes(&policy_name_settings_entry).map_err(|_e| { - ApplyError::InternalError("Invalid setting entry".to_string()) - })?; - let entrypoint_settings_entry: Setting = - Message::parse_from_bytes(&entrypoint_settings_entry).map_err(|_e| { - ApplyError::InternalError("Invalid setting entry".to_string()) - })?; - - let policy_name = policy_name_settings_entry - .get_entries() - .iter() - .next() - .ok_or_else(|| { - ApplyError::InternalError("Invalid setting entry".to_string()) - })?; - - let policy_entrypoint = entrypoint_settings_entry - .get_entries() - .iter() - .next() - .ok_or_else(|| { - ApplyError::InternalError("Invalid setting entry".to_string()) - })?; - - let policy_meta_address = - opa_tp_protocol::state::policy_meta_address(&policy_name.value); - - let policy_meta: Vec = - ctx.get_state_entry(&policy_meta_address)?.ok_or_else(|| { - ApplyError::InternalError(format!( - "Failed to load policy metadata for policy '{}' from '{}'", - policy_name.value, policy_meta_address - )) - })?; - - let policy_meta: opa_tp_protocol::state::PolicyMeta = - serde_json::from_slice(&policy_meta).map_err(|_e| { - ApplyError::InternalError(format!( - "Cannot parse policy meta for {}", - policy_name.value - )) - })?; - - debug!(policy_from_submission_meta = ?policy_meta); - - // Check if we have the policy loaded as an executor context and the - // loaded policy version against the current policy - // version. If either the policy is not loaded or the policy version is nor - // current, load the policy from the chain and cache it - if let Some((hash, executor_context)) = - self.on_chain.lock().unwrap().get(&policy_name.value) - { - if *hash == policy_meta.hash { - return Ok(executor_context.clone()); - } - } - - // Load the policy from the chain - let policy_bytes = ctx - .get_state_entry(&opa_tp_protocol::state::policy_address(&policy_name.value))? - .ok_or_else(|| { - ApplyError::InternalError(format!( - "Failed to load policy for policy {}", - policy_name.value - )) - })?; - - let loader = CliPolicyLoader::from_policy_bytes( - &policy_name.value, - &policy_entrypoint.value, - &policy_bytes, - ) - .map_err(|e| ApplyError::InternalError(e.to_string()))?; - - let ctx = ExecutorContext::from_loader(&loader) - .map_err(|e| ApplyError::InternalError(e.to_string()))?; - - self.on_chain - .lock() - .unwrap() - .insert(policy_name.value.clone(), (policy_meta.hash, ctx.clone())); - - Ok(ctx) - } - _ => Err(ApplyError::InternalError( - "Opa policy settings are invalid".to_string(), - )), - } - } + pub fn new(policy: &str, entrypoint: &str) -> Result { + Ok(Self { + on_chain: Arc::new(HashMap::new().into()), + embedded: { + ExecutorContext::from_loader( + &CliPolicyLoader::from_embedded_policy(policy, entrypoint) + .map_err(|e| ApplyError::InternalError(e.to_string()))?, + ) + .map_err(|e| ApplyError::InternalError(e.to_string()))? + }, + }) + } + + pub fn executor_context( + &self, + ctx: &dyn TransactionContext, + ) -> Result { + let policy_name_settings_entry = + ctx.get_state_entry(&sawtooth_settings_address("chronicle.opa.policy_name"))?; + + let entrypoint_settings_entry = + ctx.get_state_entry(&sawtooth_settings_address("chronicle.opa.entrypoint"))?; + + match (policy_name_settings_entry, entrypoint_settings_entry) { + (None, None) => { + warn!("Insecure operating mode - no on-chain policy name or entrypoint settings found"); + Ok(self.embedded.clone()) + }, + (Some(policy_name_settings_entry), Some(entrypoint_settings_entry)) => { + info!("Chronicle operating in secure mode - on-chain policy name and entrypoint settings found"); + let policy_name_settings_entry: Setting = + Message::parse_from_bytes(&policy_name_settings_entry).map_err(|_e| { + ApplyError::InternalError("Invalid setting entry".to_string()) + })?; + let entrypoint_settings_entry: Setting = + Message::parse_from_bytes(&entrypoint_settings_entry).map_err(|_e| { + ApplyError::InternalError("Invalid setting entry".to_string()) + })?; + + let policy_name = + policy_name_settings_entry.get_entries().iter().next().ok_or_else(|| { + ApplyError::InternalError("Invalid setting entry".to_string()) + })?; + + let policy_entrypoint = + entrypoint_settings_entry.get_entries().iter().next().ok_or_else(|| { + ApplyError::InternalError("Invalid setting entry".to_string()) + })?; + + let policy_meta_address = + opa_tp_protocol::state::policy_meta_address(&policy_name.value); + + let policy_meta: Vec = + ctx.get_state_entry(&policy_meta_address)?.ok_or_else(|| { + ApplyError::InternalError(format!( + "Failed to load policy metadata for policy '{}' from '{}'", + policy_name.value, policy_meta_address + )) + })?; + + let policy_meta: opa_tp_protocol::state::PolicyMeta = + serde_json::from_slice(&policy_meta).map_err(|_e| { + ApplyError::InternalError(format!( + "Cannot parse policy meta for {}", + policy_name.value + )) + })?; + + debug!(policy_from_submission_meta = ?policy_meta); + + // Check if we have the policy loaded as an executor context and the + // loaded policy version against the current policy + // version. If either the policy is not loaded or the policy version is nor + // current, load the policy from the chain and cache it + if let Some((hash, executor_context)) = + self.on_chain.lock().unwrap().get(&policy_name.value) + { + if *hash == policy_meta.hash { + return Ok(executor_context.clone()) + } + } + + // Load the policy from the chain + let policy_bytes = ctx + .get_state_entry(&opa_tp_protocol::state::policy_address(&policy_name.value))? + .ok_or_else(|| { + ApplyError::InternalError(format!( + "Failed to load policy for policy {}", + policy_name.value + )) + })?; + + let loader = CliPolicyLoader::from_policy_bytes( + &policy_name.value, + &policy_entrypoint.value, + &policy_bytes, + ) + .map_err(|e| ApplyError::InternalError(e.to_string()))?; + + let ctx = ExecutorContext::from_loader(&loader) + .map_err(|e| ApplyError::InternalError(e.to_string()))?; + + self.on_chain + .lock() + .unwrap() + .insert(policy_name.value.clone(), (policy_meta.hash, ctx.clone())); + + Ok(ctx) + }, + _ => Err(ApplyError::InternalError("Opa policy settings are invalid".to_string())), + } + } } diff --git a/crates/sawtooth-tp/src/tp.rs b/crates/sawtooth-tp/src/tp.rs index 04da75919..6795d4816 100644 --- a/crates/sawtooth-tp/src/tp.rs +++ b/crates/sawtooth-tp/src/tp.rs @@ -1,16 +1,16 @@ use chronicle_protocol::protocol::{ - chronicle_committed, chronicle_contradicted, chronicle_identity_from_submission, - chronicle_operations_from_submission_v1, chronicle_operations_from_submission_v2, - deserialize_submission, messages::Submission, + chronicle_committed, chronicle_contradicted, chronicle_identity_from_submission, + chronicle_operations_from_submission_v1, chronicle_operations_from_submission_v2, + deserialize_submission, messages::Submission, }; use common::{ - identity::{AuthId, OpaData, SignedIdentity}, - ledger::{OperationState, StateOutput, SubmissionError}, - opa::ExecutorContext, - prov::{ - operations::ChronicleOperation, to_json_ld::ToJson, ChronicleTransaction, - ChronicleTransactionId, ProcessorError, ProvModel, - }, + identity::{AuthId, OpaData, SignedIdentity}, + ledger::{OperationState, StateOutput, SubmissionError}, + opa::ExecutorContext, + prov::{ + operations::ChronicleOperation, to_json_ld::ToJson, ChronicleTransaction, + ChronicleTransactionId, ProcessorError, ProvModel, + }, }; use prost::Message; use std::collections::{BTreeMap, HashSet}; @@ -18,322 +18,297 @@ use std::collections::{BTreeMap, HashSet}; use chronicle_protocol::address::{SawtoothAddress, FAMILY, PREFIX, VERSION}; use sawtooth_sdk::{ - messages::processor::TpProcessRequest, - processor::handler::{ApplyError, TransactionContext, TransactionHandler}, + messages::processor::TpProcessRequest, + processor::handler::{ApplyError, TransactionContext, TransactionHandler}, }; use tracing::{error, info, instrument, trace}; use crate::{ - abstract_tp::{TPSideEffects, TP}, - opa::TpOpa, + abstract_tp::{TPSideEffects, TP}, + opa::TpOpa, }; #[derive(Debug)] pub struct ChronicleTransactionHandler { - family_name: String, - family_versions: Vec, - namespaces: Vec, - opa_executor: TpOpa, + family_name: String, + family_versions: Vec, + namespaces: Vec, + opa_executor: TpOpa, } impl ChronicleTransactionHandler { - pub fn new(policy: &str, entrypoint: &str) -> Result { - Ok(ChronicleTransactionHandler { - family_name: FAMILY.to_owned(), - family_versions: vec![VERSION.to_owned()], - namespaces: vec![PREFIX.to_string()], - opa_executor: TpOpa::new(policy, entrypoint)?, - }) - } + pub fn new(policy: &str, entrypoint: &str) -> Result { + Ok(ChronicleTransactionHandler { + family_name: FAMILY.to_owned(), + family_versions: vec![VERSION.to_owned()], + namespaces: vec![PREFIX.to_string()], + opa_executor: TpOpa::new(policy, entrypoint)?, + }) + } } #[async_trait::async_trait] impl TP for ChronicleTransactionHandler { - fn tp_parse(request: &TpProcessRequest) -> Result { - deserialize_submission(request.get_payload()) - .map_err(|e| ApplyError::InternalError(e.to_string())) - } - - fn tp_state( - context: &mut dyn TransactionContext, - operations: &ChronicleTransaction, - ) -> Result, ApplyError> { - let deps = operations - .tx - .iter() - .flat_map(|tx| tx.dependencies()) - .collect::>(); - - let addresses_to_load = deps.iter().map(SawtoothAddress::from).collect::>(); - - // Entries not present in state must be None - let sawtooth_entries = context - .get_state_entries( - &addresses_to_load - .iter() - .map(|x| x.to_string()) - .collect::>(), - )? - .into_iter() - .map(|(addr, data)| { - ( - SawtoothAddress::new(addr), - Some(String::from_utf8(data).unwrap()), - ) - }) - .collect::>(); - - let mut state = OperationState::::new(); - - let not_in_sawtooth = addresses_to_load - .iter() - .filter(|required_addr| { - !sawtooth_entries - .iter() - .any(|(addr, _)| addr == *required_addr) - }) - .map(|addr| (addr.clone(), None)) - .collect::>(); - - state.update_state(sawtooth_entries.into_iter()); - state.update_state(not_in_sawtooth.into_iter()); - - Ok(state) - } - - async fn tp_operations(submission: Submission) -> Result { - use chronicle_protocol::protocol::messages::submission::IdentityVariant; - use common::prov::{transaction, transaction::ToChronicleTransaction}; - - let identity = chronicle_identity_from_submission( - match submission - .identity_variant - .ok_or_else(|| ApplyError::InternalError("missing identity".to_string()))? - { - IdentityVariant::IdentityOld(id) => id, - IdentityVariant::Identity(id) => id.payload, - }, - ) - .await - .map_err(|e| ApplyError::InternalError(e.to_string()))?; - match &*submission.version { - "1" => { - use transaction::v1::ChronicleTransaction; - #[allow(deprecated)] - let ops = chronicle_operations_from_submission_v1(submission.body_old) - .await - .map_err(|e| ApplyError::InternalError(e.to_string()))?; - let tx = ChronicleTransaction::new(ops, identity); - Ok(tx.to_current()) - } - "2" => { - use transaction::v2::ChronicleTransaction; - let ops = chronicle_operations_from_submission_v2( - match submission.body_variant.unwrap() { - chronicle_protocol::protocol::messages::submission::BodyVariant::Body( - body, - ) => body.payload, - }, - ) - .await - .map_err(|e| ApplyError::InternalError(e.to_string()))?; - let tx = ChronicleTransaction::new(ops, identity); - Ok(tx.to_current()) - } - v => Err(ApplyError::InternalError(format!( - "unknown protocol version: {v}" - ))), - } - } - - async fn tp( - opa_executor: ExecutorContext, - request: &TpProcessRequest, - submission: Submission, - operations: ChronicleTransaction, - mut state: OperationState, - ) -> Result { - let mut effects = TPSideEffects::new(); - - let _protocol_version = submission.version; - let span = submission.span_id; - let id = request.get_signature().to_owned(); - - info!(transaction_id = %id, span = %span, operation_count = %operations.tx.len(), identity = ?submission.identity_variant); - - //precompute dependencies - let deps = operations - .tx - .iter() - .flat_map(|tx| tx.dependencies()) - .collect::>(); - - let deps_as_sawtooth = deps - .iter() - .map(SawtoothAddress::from) - .collect::>(); - - trace!( - input_chronicle_addresses=?deps, - ); - - let mut model = ProvModel::default(); - - // Now apply operations to the model - for operation in operations.tx { - Self::enforce_opa( - opa_executor.clone(), - &operations.identity, - &operation, - &state, - ) - .await?; - - let res = operation.process(model, state.input()).await; - match res { - // A contradiction raises an event and shortcuts processing - Err(ProcessorError::Contradiction(source)) => { - info!(contradiction = %source); - let ev = chronicle_contradicted(span, &source, &operations.identity) - .map_err(|e| ApplyError::InternalError(e.to_string()))?; - effects.add_event( - "chronicle/prov-update".to_string(), - vec![("transaction_id".to_owned(), request.signature.clone())], - ev.encode_to_vec(), - ); - return Ok(effects); - } - // Severe errors should be logged - Err(e) => { - error!(chronicle_prov_failure = %e); - - return Ok(effects); - } - Ok((tx_output, updated_model)) => { - state.update_state( - tx_output - .into_iter() - .map(|output| { - trace!(output_state = %output.data); - (SawtoothAddress::from(&output.address), Some(output.data)) - }) - .collect::>() - .into_iter(), - ); - model = updated_model; - } - } - } - - let dirty = state.dirty().collect::>(); - - trace!(dirty = ?dirty); - - let mut delta = ProvModel::default(); - for output in dirty - .into_iter() - .map(|output: StateOutput| { - if deps_as_sawtooth.contains(&output.address) { - Ok(output) - } else { - Err(SubmissionError::processor( - &ChronicleTransactionId::from(&*id), - ProcessorError::Address {}, - )) - } - }) - .collect::, SubmissionError>>() - .into_iter() - .flat_map(|v: Vec>| v.into_iter()) - { - let state: serde_json::Value = serde_json::from_str(&output.data) - .map_err(|e| ApplyError::InternalError(e.to_string()))?; - - delta - .apply_json_ld_str(&output.data) - .await - .map_err(|e| ApplyError::InternalError(e.to_string()))?; - - effects.set_state_entry( - output.address.to_string(), - serde_json::to_vec(&state).map_err(|e| ApplyError::InternalError(e.to_string()))?, - ) - } - - // Finally emit the delta as an event - let ev = chronicle_committed(span, delta, &operations.identity) - .await - .map_err(|e| ApplyError::InternalError(e.to_string()))?; - - effects.add_event( - "chronicle/prov-update".to_string(), - vec![("transaction_id".to_owned(), request.signature.clone())], - ev.encode_to_vec(), - ); - - Ok(effects) - } - - /// Get identity, the content of the compact json-ld representation of the operation, and a more - /// readable serialization of the @graph from a provmodel containing the operation's dependencies and then - /// pass them as the context to an OPA rule check, returning an error upon OPA policy failure - async fn enforce_opa( - opa_executor: ExecutorContext, - identity: &SignedIdentity, - tx: &ChronicleOperation, - state: &OperationState, - ) -> Result<(), ApplyError> { - let identity = AuthId::try_from(identity) - .map_err(|e| ApplyError::InternalError(ProcessorError::SerdeJson(e).to_string()))?; - - // Set up Context for OPA rule check - let operation = - tx.to_json().compact().await.map_err(|e| { - ApplyError::InternalError(ProcessorError::Compaction(e).to_string()) - })?; - - // Get the dependencies - let deps = tx - .dependencies() - .into_iter() - .collect::>() - .iter() - .map(SawtoothAddress::from) - .collect::>(); - - let operation_state = state.opa_context(deps); - - let state = serde_json::Value::Array( - tx.opa_context_state(ProvModel::default(), operation_state) - .await - .map_err(|e| ApplyError::InternalError(e.to_string()))?, - ); - - let opa_data = OpaData::operation(&identity, &operation, &state); - - info!(opa_evaluation_context = ?opa_data); - - match opa_executor.evaluate(&identity, &opa_data).await { - Ok(()) => Ok(()), - Err(e) => Err(ApplyError::InvalidTransaction(e.to_string())), - } - } + fn tp_parse(request: &TpProcessRequest) -> Result { + deserialize_submission(request.get_payload()) + .map_err(|e| ApplyError::InternalError(e.to_string())) + } + + fn tp_state( + context: &mut dyn TransactionContext, + operations: &ChronicleTransaction, + ) -> Result, ApplyError> { + let deps = operations.tx.iter().flat_map(|tx| tx.dependencies()).collect::>(); + + let addresses_to_load = deps.iter().map(SawtoothAddress::from).collect::>(); + + // Entries not present in state must be None + let sawtooth_entries = context + .get_state_entries( + &addresses_to_load.iter().map(|x| x.to_string()).collect::>(), + )? + .into_iter() + .map(|(addr, data)| { + (SawtoothAddress::new(addr), Some(String::from_utf8(data).unwrap())) + }) + .collect::>(); + + let mut state = OperationState::::new(); + + let not_in_sawtooth = addresses_to_load + .iter() + .filter(|required_addr| { + !sawtooth_entries.iter().any(|(addr, _)| addr == *required_addr) + }) + .map(|addr| (addr.clone(), None)) + .collect::>(); + + state.update_state(sawtooth_entries.into_iter()); + state.update_state(not_in_sawtooth.into_iter()); + + Ok(state) + } + + async fn tp_operations(submission: Submission) -> Result { + use chronicle_protocol::protocol::messages::submission::IdentityVariant; + use common::prov::{transaction, transaction::ToChronicleTransaction}; + + let identity = chronicle_identity_from_submission( + match submission + .identity_variant + .ok_or_else(|| ApplyError::InternalError("missing identity".to_string()))? + { + IdentityVariant::IdentityOld(id) => id, + IdentityVariant::Identity(id) => id.payload, + }, + ) + .await + .map_err(|e| ApplyError::InternalError(e.to_string()))?; + match &*submission.version { + "1" => { + use transaction::v1::ChronicleTransaction; + #[allow(deprecated)] + let ops = chronicle_operations_from_submission_v1(submission.body_old) + .await + .map_err(|e| ApplyError::InternalError(e.to_string()))?; + let tx = ChronicleTransaction::new(ops, identity); + Ok(tx.to_current()) + }, + "2" => { + use transaction::v2::ChronicleTransaction; + let ops = chronicle_operations_from_submission_v2( + match submission.body_variant.unwrap() { + chronicle_protocol::protocol::messages::submission::BodyVariant::Body( + body, + ) => body.payload, + }, + ) + .await + .map_err(|e| ApplyError::InternalError(e.to_string()))?; + let tx = ChronicleTransaction::new(ops, identity); + Ok(tx.to_current()) + }, + v => Err(ApplyError::InternalError(format!("unknown protocol version: {v}"))), + } + } + + async fn tp( + opa_executor: ExecutorContext, + request: &TpProcessRequest, + submission: Submission, + operations: ChronicleTransaction, + mut state: OperationState, + ) -> Result { + let mut effects = TPSideEffects::new(); + + let _protocol_version = submission.version; + let span = submission.span_id; + let id = request.get_signature().to_owned(); + + info!(transaction_id = %id, span = %span, operation_count = %operations.tx.len(), identity = ?submission.identity_variant); + + //precompute dependencies + let deps = operations.tx.iter().flat_map(|tx| tx.dependencies()).collect::>(); + + let deps_as_sawtooth = deps.iter().map(SawtoothAddress::from).collect::>(); + + trace!( + input_chronicle_addresses=?deps, + ); + + let mut model = ProvModel::default(); + + // Now apply operations to the model + for operation in operations.tx { + Self::enforce_opa(opa_executor.clone(), &operations.identity, &operation, &state) + .await?; + + let res = operation.process(model, state.input()).await; + match res { + // A contradiction raises an event and shortcuts processing + Err(ProcessorError::Contradiction(source)) => { + info!(contradiction = %source); + let ev = chronicle_contradicted(span, &source, &operations.identity) + .map_err(|e| ApplyError::InternalError(e.to_string()))?; + effects.add_event( + "chronicle/prov-update".to_string(), + vec![("transaction_id".to_owned(), request.signature.clone())], + ev.encode_to_vec(), + ); + return Ok(effects) + }, + // Severe errors should be logged + Err(e) => { + error!(chronicle_prov_failure = %e); + + return Ok(effects) + }, + Ok((tx_output, updated_model)) => { + state.update_state( + tx_output + .into_iter() + .map(|output| { + trace!(output_state = %output.data); + (SawtoothAddress::from(&output.address), Some(output.data)) + }) + .collect::>() + .into_iter(), + ); + model = updated_model; + }, + } + } + + let dirty = state.dirty().collect::>(); + + trace!(dirty = ?dirty); + + let mut delta = ProvModel::default(); + for output in dirty + .into_iter() + .map(|output: StateOutput| { + if deps_as_sawtooth.contains(&output.address) { + Ok(output) + } else { + Err(SubmissionError::processor( + &ChronicleTransactionId::from(&*id), + ProcessorError::Address {}, + )) + } + }) + .collect::, SubmissionError>>() + .into_iter() + .flat_map(|v: Vec>| v.into_iter()) + { + let state: serde_json::Value = serde_json::from_str(&output.data) + .map_err(|e| ApplyError::InternalError(e.to_string()))?; + + delta + .apply_json_ld_str(&output.data) + .await + .map_err(|e| ApplyError::InternalError(e.to_string()))?; + + effects.set_state_entry( + output.address.to_string(), + serde_json::to_vec(&state).map_err(|e| ApplyError::InternalError(e.to_string()))?, + ) + } + + // Finally emit the delta as an event + let ev = chronicle_committed(span, delta, &operations.identity) + .await + .map_err(|e| ApplyError::InternalError(e.to_string()))?; + + effects.add_event( + "chronicle/prov-update".to_string(), + vec![("transaction_id".to_owned(), request.signature.clone())], + ev.encode_to_vec(), + ); + + Ok(effects) + } + + /// Get identity, the content of the compact json-ld representation of the operation, and a more + /// readable serialization of the @graph from a provmodel containing the operation's + /// dependencies and then pass them as the context to an OPA rule check, returning an error upon + /// OPA policy failure + async fn enforce_opa( + opa_executor: ExecutorContext, + identity: &SignedIdentity, + tx: &ChronicleOperation, + state: &OperationState, + ) -> Result<(), ApplyError> { + let identity = AuthId::try_from(identity) + .map_err(|e| ApplyError::InternalError(ProcessorError::SerdeJson(e).to_string()))?; + + // Set up Context for OPA rule check + let operation = + tx.to_json().compact().await.map_err(|e| { + ApplyError::InternalError(ProcessorError::Compaction(e).to_string()) + })?; + + // Get the dependencies + let deps = tx + .dependencies() + .into_iter() + .collect::>() + .iter() + .map(SawtoothAddress::from) + .collect::>(); + + let operation_state = state.opa_context(deps); + + let state = serde_json::Value::Array( + tx.opa_context_state(ProvModel::default(), operation_state) + .await + .map_err(|e| ApplyError::InternalError(e.to_string()))?, + ); + + let opa_data = OpaData::operation(&identity, &operation, &state); + + info!(opa_evaluation_context = ?opa_data); + + match opa_executor.evaluate(&identity, &opa_data).await { + Ok(()) => Ok(()), + Err(e) => Err(ApplyError::InvalidTransaction(e.to_string())), + } + } } impl TransactionHandler for ChronicleTransactionHandler { - fn family_name(&self) -> String { - self.family_name.clone() - } + fn family_name(&self) -> String { + self.family_name.clone() + } - fn family_versions(&self) -> Vec { - self.family_versions.clone() - } + fn family_versions(&self) -> Vec { + self.family_versions.clone() + } - fn namespaces(&self) -> Vec { - self.namespaces.clone() - } + fn namespaces(&self) -> Vec { + self.namespaces.clone() + } - #[instrument( + #[instrument( name = "apply", level = "debug", skip(request,context), @@ -344,300 +319,272 @@ impl TransactionHandler for ChronicleTransactionHandler { dependencies = ?request.header.as_ref().map(|x| &x.dependencies) ) )] - fn apply( - &self, - request: &TpProcessRequest, - context: &mut dyn TransactionContext, - ) -> Result<(), ApplyError> { - let submission = Self::tp_parse(request)?; - let submission_clone = submission.clone(); - - let opa_exec_context = self.opa_executor.executor_context(context)?; - - let operations = - futures::executor::block_on( - async move { Self::tp_operations(submission.clone()).await }, - )?; - - info!(transaction_id = %request.signature, operation_count = %operations.tx.len()); - - let state = Self::tp_state(context, &operations)?; - let effects = futures::executor::block_on(async move { - Self::tp( - opa_exec_context, - request, - submission_clone, - operations, - state, - ) - .await - }) - .map_err(|e| ApplyError::InternalError(e.to_string()))?; - - effects - .apply(context) - .map_err(|e| ApplyError::InternalError(e.to_string())) - } + fn apply( + &self, + request: &TpProcessRequest, + context: &mut dyn TransactionContext, + ) -> Result<(), ApplyError> { + let submission = Self::tp_parse(request)?; + let submission_clone = submission.clone(); + + let opa_exec_context = self.opa_executor.executor_context(context)?; + + let operations = + futures::executor::block_on( + async move { Self::tp_operations(submission.clone()).await }, + )?; + + info!(transaction_id = %request.signature, operation_count = %operations.tx.len()); + + let state = Self::tp_state(context, &operations)?; + let effects = futures::executor::block_on(async move { + Self::tp(opa_exec_context, request, submission_clone, operations, state).await + }) + .map_err(|e| ApplyError::InternalError(e.to_string()))?; + + effects.apply(context).map_err(|e| ApplyError::InternalError(e.to_string())) + } } #[cfg(test)] pub mod test { - use std::{ - cell::RefCell, - collections::{hash_map::DefaultHasher, BTreeMap}, - hash::{Hash, Hasher}, - }; - - use async_stl_client::sawtooth::TransactionPayload; - use chronicle_protocol::{ - async_stl_client::{ledger::LedgerTransaction, sawtooth::MessageBuilder}, - messages::ChronicleSubmitTransaction, - protocol::messages::Submission, - }; - use chronicle_signing::{ - chronicle_secret_names, ChronicleSecretsOptions, ChronicleSigning, BATCHER_NAMESPACE, - CHRONICLE_NAMESPACE, - }; - use chrono::{NaiveDateTime, TimeZone, Utc}; - use common::{ - identity::{AuthId, SignedIdentity}, - prov::{ - operations::{ - ActsOnBehalfOf, AgentExists, ChronicleOperation, CreateNamespace, EndActivity, - StartActivity, - }, - ActivityId, AgentId, ChronicleTransaction, DelegationId, ExternalId, ExternalIdPart, - NamespaceId, Role, - }, - }; - use prost::Message; - - use sawtooth_sdk::{ - messages::{processor::TpProcessRequest, transaction::TransactionHeader}, - processor::handler::{ContextError, TransactionContext, TransactionHandler}, - }; - use serde_json::Value; - - use uuid::Uuid; - - use crate::{abstract_tp::TP, tp::ChronicleTransactionHandler}; - - type TestTxEvents = Vec<(String, Vec<(String, String)>, Vec)>; - - pub struct TestTransactionContext { - pub state: RefCell>>, - pub events: RefCell, - } - - type PrintableEvent = Vec<(String, Vec<(String, String)>, Value)>; - - impl TestTransactionContext { - pub fn new() -> Self { - Self { - state: RefCell::new(BTreeMap::new()), - events: RefCell::new(vec![]), - } - } - - pub fn readable_state(&self) -> Vec<(String, Value)> { - self.state - .borrow() - .iter() - .map(|(k, v)| { - ( - k.clone(), - serde_json::from_str(&String::from_utf8(v.clone()).unwrap()).unwrap(), - ) - }) - .collect() - } - - pub fn readable_events(&self) -> PrintableEvent { - self.events - .borrow() - .iter() - .map(|(k, attr, data)| { - ( - k.clone(), - attr.clone(), - serde_json::from_str( - &chronicle_protocol::sawtooth::Event::decode(&**data) - .unwrap() - .delta, - ) - .unwrap(), - ) - }) - .collect() - } - } - - impl Default for TestTransactionContext { - fn default() -> Self { - Self::new() - } - } - - impl TransactionContext for TestTransactionContext { - fn add_receipt_data( - self: &TestTransactionContext, - _data: &[u8], - ) -> Result<(), ContextError> { - unimplemented!() - } - - fn add_event( - self: &TestTransactionContext, - event_type: String, - attributes: Vec<(String, String)>, - data: &[u8], - ) -> Result<(), ContextError> { - self.events - .borrow_mut() - .push((event_type, attributes, data.to_vec())); - Ok(()) - } - - fn delete_state_entries( - self: &TestTransactionContext, - _addresses: &[std::string::String], - ) -> Result, ContextError> { - unimplemented!() - } - - fn get_state_entries( - &self, - addresses: &[String], - ) -> Result)>, ContextError> { - Ok(self - .state - .borrow() - .iter() - .filter(|(k, _)| addresses.contains(k)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect()) - } - - fn set_state_entries( - self: &TestTransactionContext, - entries: Vec<(String, Vec)>, - ) -> std::result::Result<(), sawtooth_sdk::processor::handler::ContextError> { - for entry in entries { - self.state.borrow_mut().insert(entry.0, entry.1); - } - - Ok(()) - } - } - - #[derive(Debug, Clone)] - struct SameUuid; - - fn uuid() -> Uuid { - Uuid::parse_str("5a0ab5b8-eeb7-4812-9fe3-6dd69bd20cea").unwrap() - } - - fn create_namespace_id_helper(tag: Option) -> NamespaceId { - let external_id = if tag.is_none() || tag == Some(0) { - "testns".to_string() - } else { - format!("testns{}", tag.unwrap()) - }; - NamespaceId::from_external_id(external_id, uuid()) - } - - fn create_namespace_helper(tag: Option) -> ChronicleOperation { - let id = create_namespace_id_helper(tag); - let external_id = &id.external_id_part().to_string(); - ChronicleOperation::CreateNamespace(CreateNamespace::new(id, external_id, uuid())) - } - - fn agent_exists_helper() -> ChronicleOperation { - let namespace: NamespaceId = NamespaceId::from_external_id("testns", uuid()); - let external_id: ExternalId = - ExternalIdPart::external_id_part(&AgentId::from_external_id("test_agent")).clone(); - ChronicleOperation::AgentExists(AgentExists { - namespace, - external_id, - }) - } - - fn create_agent_acts_on_behalf_of() -> ChronicleOperation { - let namespace: NamespaceId = NamespaceId::from_external_id("testns", uuid()); - let responsible_id = AgentId::from_external_id("test_agent"); - let delegate_id = AgentId::from_external_id("test_delegate"); - let activity_id = ActivityId::from_external_id("test_activity"); - let role = "test_role"; - let id = DelegationId::from_component_ids( - &delegate_id, - &responsible_id, - Some(&activity_id), - Some(role), - ); - let role = Role::from(role.to_string()); - ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf { - namespace, - id, - responsible_id, - delegate_id, - activity_id: Some(activity_id), - role: Some(role), - }) - } - - #[tokio::test] - async fn simple_non_contradicting_operation() { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - - let secrets = ChronicleSigning::new( - chronicle_secret_names(), - vec![ - ( - CHRONICLE_NAMESPACE.to_string(), - ChronicleSecretsOptions::test_keys(), - ), - ( - BATCHER_NAMESPACE.to_string(), - ChronicleSecretsOptions::test_keys(), - ), - ], - ) - .await - .unwrap(); - let signed_identity = AuthId::chronicle().signed_identity(&secrets).unwrap(); - - // Example transaction payload of `CreateNamespace`, - // `AgentExists`, and `AgentActsOnBehalfOf` `ChronicleOperation`s - let tx = ChronicleTransaction::new( - vec![ - create_namespace_helper(None), - agent_exists_helper(), - create_agent_acts_on_behalf_of(), - ], - signed_identity, - ); - - let submit_tx = ChronicleSubmitTransaction { - tx, - signer: secrets.clone(), - policy_name: None, - }; - - let message_builder = MessageBuilder::new_deterministic("TEST", "1.0"); - // Get a signed tx from sawtooth protocol - let (tx, _id) = submit_tx.as_sawtooth_tx(&message_builder).await.unwrap(); - - let header = - ::parse_from_bytes(&tx.header).unwrap(); - - let mut request = TpProcessRequest::default(); - request.set_header(header); - request.set_payload(tx.payload); - request.set_signature("TRANSACTION_SIGNATURE".to_string()); - - let (policy, entrypoint) = ("allow_transactions", "allow_transactions.allowed_users"); - - tokio::task::spawn_blocking(move || { + use std::{ + cell::RefCell, + collections::{hash_map::DefaultHasher, BTreeMap}, + hash::{Hash, Hasher}, + }; + + use async_stl_client::sawtooth::TransactionPayload; + use chronicle_protocol::{ + async_stl_client::{ledger::LedgerTransaction, sawtooth::MessageBuilder}, + messages::ChronicleSubmitTransaction, + protocol::messages::Submission, + }; + use chronicle_signing::{ + chronicle_secret_names, ChronicleSecretsOptions, ChronicleSigning, BATCHER_NAMESPACE, + CHRONICLE_NAMESPACE, + }; + use chrono::{NaiveDateTime, TimeZone, Utc}; + use common::{ + identity::{AuthId, SignedIdentity}, + prov::{ + operations::{ + ActsOnBehalfOf, AgentExists, ChronicleOperation, CreateNamespace, EndActivity, + StartActivity, + }, + ActivityId, AgentId, ChronicleTransaction, DelegationId, ExternalId, ExternalIdPart, + NamespaceId, Role, + }, + }; + use prost::Message; + + use sawtooth_sdk::{ + messages::{processor::TpProcessRequest, transaction::TransactionHeader}, + processor::handler::{ContextError, TransactionContext, TransactionHandler}, + }; + use serde_json::Value; + + use uuid::Uuid; + + use crate::{abstract_tp::TP, tp::ChronicleTransactionHandler}; + + type TestTxEvents = Vec<(String, Vec<(String, String)>, Vec)>; + + pub struct TestTransactionContext { + pub state: RefCell>>, + pub events: RefCell, + } + + type PrintableEvent = Vec<(String, Vec<(String, String)>, Value)>; + + impl TestTransactionContext { + pub fn new() -> Self { + Self { state: RefCell::new(BTreeMap::new()), events: RefCell::new(vec![]) } + } + + pub fn readable_state(&self) -> Vec<(String, Value)> { + self.state + .borrow() + .iter() + .map(|(k, v)| { + ( + k.clone(), + serde_json::from_str(&String::from_utf8(v.clone()).unwrap()).unwrap(), + ) + }) + .collect() + } + + pub fn readable_events(&self) -> PrintableEvent { + self.events + .borrow() + .iter() + .map(|(k, attr, data)| { + ( + k.clone(), + attr.clone(), + serde_json::from_str( + &chronicle_protocol::sawtooth::Event::decode(&**data).unwrap().delta, + ) + .unwrap(), + ) + }) + .collect() + } + } + + impl Default for TestTransactionContext { + fn default() -> Self { + Self::new() + } + } + + impl TransactionContext for TestTransactionContext { + fn add_receipt_data( + self: &TestTransactionContext, + _data: &[u8], + ) -> Result<(), ContextError> { + unimplemented!() + } + + fn add_event( + self: &TestTransactionContext, + event_type: String, + attributes: Vec<(String, String)>, + data: &[u8], + ) -> Result<(), ContextError> { + self.events.borrow_mut().push((event_type, attributes, data.to_vec())); + Ok(()) + } + + fn delete_state_entries( + self: &TestTransactionContext, + _addresses: &[std::string::String], + ) -> Result, ContextError> { + unimplemented!() + } + + fn get_state_entries( + &self, + addresses: &[String], + ) -> Result)>, ContextError> { + Ok(self + .state + .borrow() + .iter() + .filter(|(k, _)| addresses.contains(k)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect()) + } + + fn set_state_entries( + self: &TestTransactionContext, + entries: Vec<(String, Vec)>, + ) -> std::result::Result<(), sawtooth_sdk::processor::handler::ContextError> { + for entry in entries { + self.state.borrow_mut().insert(entry.0, entry.1); + } + + Ok(()) + } + } + + #[derive(Debug, Clone)] + struct SameUuid; + + fn uuid() -> Uuid { + Uuid::parse_str("5a0ab5b8-eeb7-4812-9fe3-6dd69bd20cea").unwrap() + } + + fn create_namespace_id_helper(tag: Option) -> NamespaceId { + let external_id = if tag.is_none() || tag == Some(0) { + "testns".to_string() + } else { + format!("testns{}", tag.unwrap()) + }; + NamespaceId::from_external_id(external_id, uuid()) + } + + fn create_namespace_helper(tag: Option) -> ChronicleOperation { + let id = create_namespace_id_helper(tag); + let external_id = &id.external_id_part().to_string(); + ChronicleOperation::CreateNamespace(CreateNamespace::new(id, external_id, uuid())) + } + + fn agent_exists_helper() -> ChronicleOperation { + let namespace: NamespaceId = NamespaceId::from_external_id("testns", uuid()); + let external_id: ExternalId = + ExternalIdPart::external_id_part(&AgentId::from_external_id("test_agent")).clone(); + ChronicleOperation::AgentExists(AgentExists { namespace, external_id }) + } + + fn create_agent_acts_on_behalf_of() -> ChronicleOperation { + let namespace: NamespaceId = NamespaceId::from_external_id("testns", uuid()); + let responsible_id = AgentId::from_external_id("test_agent"); + let delegate_id = AgentId::from_external_id("test_delegate"); + let activity_id = ActivityId::from_external_id("test_activity"); + let role = "test_role"; + let id = DelegationId::from_component_ids( + &delegate_id, + &responsible_id, + Some(&activity_id), + Some(role), + ); + let role = Role::from(role.to_string()); + ChronicleOperation::AgentActsOnBehalfOf(ActsOnBehalfOf { + namespace, + id, + responsible_id, + delegate_id, + activity_id: Some(activity_id), + role: Some(role), + }) + } + + #[tokio::test] + async fn simple_non_contradicting_operation() { + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + + let secrets = ChronicleSigning::new( + chronicle_secret_names(), + vec![ + (CHRONICLE_NAMESPACE.to_string(), ChronicleSecretsOptions::test_keys()), + (BATCHER_NAMESPACE.to_string(), ChronicleSecretsOptions::test_keys()), + ], + ) + .await + .unwrap(); + let signed_identity = AuthId::chronicle().signed_identity(&secrets).unwrap(); + + // Example transaction payload of `CreateNamespace`, + // `AgentExists`, and `AgentActsOnBehalfOf` `ChronicleOperation`s + let tx = ChronicleTransaction::new( + vec![ + create_namespace_helper(None), + agent_exists_helper(), + create_agent_acts_on_behalf_of(), + ], + signed_identity, + ); + + let submit_tx = + ChronicleSubmitTransaction { tx, signer: secrets.clone(), policy_name: None }; + + let message_builder = MessageBuilder::new_deterministic("TEST", "1.0"); + // Get a signed tx from sawtooth protocol + let (tx, _id) = submit_tx.as_sawtooth_tx(&message_builder).await.unwrap(); + + let header = + ::parse_from_bytes(&tx.header).unwrap(); + + let mut request = TpProcessRequest::default(); + request.set_header(header); + request.set_payload(tx.payload); + request.set_signature("TRANSACTION_SIGNATURE".to_string()); + + let (policy, entrypoint) = ("allow_transactions", "allow_transactions.allowed_users"); + + tokio::task::spawn_blocking(move || { // Create a `TestTransactionContext` to pass to the `tp` function let mut context = TestTransactionContext::new(); let handler = ChronicleTransactionHandler::new(policy, entrypoint).unwrap(); @@ -724,73 +671,62 @@ pub mod test { }) .await .unwrap(); - } - - pub fn construct_operations() -> Vec { - let mut hasher = DefaultHasher::new(); - "foo".hash(&mut hasher); - let n1 = hasher.finish(); - "bar".hash(&mut hasher); - let n2 = hasher.finish(); - let uuid = Uuid::from_u64_pair(n1, n2); - - let base_ms = 1234567654321; - let activity_start = - Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_millis(base_ms).unwrap()); - let activity_end = - Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_millis(base_ms + 12345).unwrap()); - - let start = ChronicleOperation::StartActivity(StartActivity { - namespace: NamespaceId::from_external_id("test-namespace", uuid), - id: ActivityId::from_external_id("test-activity"), - time: activity_start, - }); - let end = ChronicleOperation::EndActivity(EndActivity { - namespace: NamespaceId::from_external_id("test-namespace", uuid), - id: ActivityId::from_external_id("test-activity"), - time: activity_end, - }); - - vec![start, end] - } - - async fn construct_submission(operations: Vec) -> Submission { - let secrets = ChronicleSigning::new( - chronicle_secret_names(), - vec![ - ( - CHRONICLE_NAMESPACE.to_string(), - ChronicleSecretsOptions::test_keys(), - ), - ( - BATCHER_NAMESPACE.to_string(), - ChronicleSecretsOptions::test_keys(), - ), - ], - ) - .await - .unwrap(); - - let tx_sub = ChronicleSubmitTransaction::new( - ChronicleTransaction { - tx: operations, - identity: SignedIdentity::new_no_identity(), - }, - secrets, - None, - ); - Submission::decode(tx_sub.to_bytes().await.unwrap().as_slice()).unwrap() - } - - #[tokio::test] - async fn get_operations_from_submission() { - chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); - let expected_operations = construct_operations(); - let submission = construct_submission(expected_operations.clone()).await; - let actual_operations = ChronicleTransactionHandler::tp_operations(submission) - .await - .unwrap() - .tx; - assert_eq!(expected_operations, actual_operations); - } + } + + pub fn construct_operations() -> Vec { + let mut hasher = DefaultHasher::new(); + "foo".hash(&mut hasher); + let n1 = hasher.finish(); + "bar".hash(&mut hasher); + let n2 = hasher.finish(); + let uuid = Uuid::from_u64_pair(n1, n2); + + let base_ms = 1234567654321; + let activity_start = + Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_millis(base_ms).unwrap()); + let activity_end = + Utc.from_utc_datetime(&NaiveDateTime::from_timestamp_millis(base_ms + 12345).unwrap()); + + let start = ChronicleOperation::StartActivity(StartActivity { + namespace: NamespaceId::from_external_id("test-namespace", uuid), + id: ActivityId::from_external_id("test-activity"), + time: activity_start, + }); + let end = ChronicleOperation::EndActivity(EndActivity { + namespace: NamespaceId::from_external_id("test-namespace", uuid), + id: ActivityId::from_external_id("test-activity"), + time: activity_end, + }); + + vec![start, end] + } + + async fn construct_submission(operations: Vec) -> Submission { + let secrets = ChronicleSigning::new( + chronicle_secret_names(), + vec![ + (CHRONICLE_NAMESPACE.to_string(), ChronicleSecretsOptions::test_keys()), + (BATCHER_NAMESPACE.to_string(), ChronicleSecretsOptions::test_keys()), + ], + ) + .await + .unwrap(); + + let tx_sub = ChronicleSubmitTransaction::new( + ChronicleTransaction { tx: operations, identity: SignedIdentity::new_no_identity() }, + secrets, + None, + ); + Submission::decode(tx_sub.to_bytes().await.unwrap().as_slice()).unwrap() + } + + #[tokio::test] + async fn get_operations_from_submission() { + chronicle_telemetry::telemetry(None, chronicle_telemetry::ConsoleLogging::Pretty); + let expected_operations = construct_operations(); + let submission = construct_submission(expected_operations.clone()).await; + let actual_operations = + ChronicleTransactionHandler::tp_operations(submission).await.unwrap().tx; + assert_eq!(expected_operations, actual_operations); + } } diff --git a/node/Containerfile b/node/Containerfile new file mode 100644 index 000000000..01a4a54f4 --- /dev/null +++ b/node/Containerfile @@ -0,0 +1,31 @@ +FROM docker.io/library/ubuntu:22.04 + +# show backtraces +ENV RUST_BACKTRACE 1 + +# install tools and dependencies +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates && \ +# apt cleanup + apt-get autoremove -y && \ + apt-get clean && \ + find /var/lib/apt/lists/ -type f -not -name lock -delete; \ +# add user and link ~/.local/share/polkadot to /data + useradd -m -u 1000 -U -s /bin/sh -d /polkadot polkadot && \ + mkdir -p /data /polkadot/.local/share && \ + chown -R polkadot:polkadot /data && \ + ln -s /data /polkadot/.local/share/node-template + +USER polkadot + +# copy the compiled binary to the container +COPY --chown=polkadot:polkadot --chmod=774 node-template /usr/bin/node-template + +# check if executable works in this container +RUN /usr/bin/node-template --version + +# ws_port +EXPOSE 9930 9333 9944 30333 30334 + +CMD ["/usr/bin/node-template"] diff --git a/node/LICENSE b/node/LICENSE new file mode 100644 index 000000000..ffa0b3f2d --- /dev/null +++ b/node/LICENSE @@ -0,0 +1,16 @@ +MIT No Attribution + +Copyright Parity Technologies (UK) Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node/README.md b/node/README.md new file mode 100644 index 000000000..337facaaf --- /dev/null +++ b/node/README.md @@ -0,0 +1,164 @@ +# Substrate Node Template + +A fresh [Substrate](https://substrate.io/) node, ready for hacking :rocket: + +A standalone version of this template is available for each release of Polkadot in the [Substrate Developer Hub Parachain Template](https://github.com/substrate-developer-hub/substrate-parachain-template/) repository. +The parachain template is generated directly at each Polkadot release branch from the [Node Template in Substrate](https://github.com/paritytech/substrate/tree/master/bin/node-template) upstream + +It is usually best to use the stand-alone version to start a new project. +All bugs, suggestions, and feature requests should be made upstream in the [Substrate](https://github.com/paritytech/substrate/tree/master/bin/node-template) repository. + +## Getting Started + +Depending on your operating system and Rust version, there might be additional packages required to compile this template. +Check the [Install](https://docs.substrate.io/install/) instructions for your platform for the most common dependencies. +Alternatively, you can use one of the [alternative installation](#alternatives-installations) options. + +### Build + +Use the following command to build the node without launching it: + +```sh +cargo build --release +``` + +### Embedded Docs + +After you build the project, you can use the following command to explore its parameters and subcommands: + +```sh +./target/release/node-template -h +``` + +You can generate and view the [Rust Docs](https://doc.rust-lang.org/cargo/commands/cargo-doc.html) for this template with this command: + +```sh +cargo +nightly doc --open +``` + +### Single-Node Development Chain + +The following command starts a single-node development chain that doesn't persist state: + +```sh +./target/release/node-template --dev +``` + +To purge the development chain's state, run the following command: + +```sh +./target/release/node-template purge-chain --dev +``` + +To start the development chain with detailed logging, run the following command: + +```sh +RUST_BACKTRACE=1 ./target/release/node-template -ldebug --dev +``` + +Development chains: + +- Maintain state in a `tmp` folder while the node is running. +- Use the **Alice** and **Bob** accounts as default validator authorities. +- Use the **Alice** account as the default `sudo` account. +- Are preconfigured with a genesis state (`/node/src/chain_spec.rs`) that includes several prefunded development accounts. + + +To persist chain state between runs, specify a base path by running a command similar to the following: + +```sh +// Create a folder to use as the db base path +$ mkdir my-chain-state + +// Use of that folder to store the chain state +$ ./target/release/node-template --dev --base-path ./my-chain-state/ + +// Check the folder structure created inside the base path after running the chain +$ ls ./my-chain-state +chains +$ ls ./my-chain-state/chains/ +dev +$ ls ./my-chain-state/chains/dev +db keystore network +``` + +### Connect with Polkadot-JS Apps Front-End + +After you start the node template locally, you can interact with it using the hosted version of the [Polkadot/Substrate Portal](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) front-end by connecting to the local node endpoint. +A hosted version is also available on [IPFS (redirect) here](https://dotapps.io/) or [IPNS (direct) here](ipns://dotapps.io/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/explorer). +You can also find the source code and instructions for hosting your own instance on the [polkadot-js/apps](https://github.com/polkadot-js/apps) repository. + +### Multi-Node Local Testnet + +If you want to see the multi-node consensus algorithm in action, see [Simulate a network](https://docs.substrate.io/tutorials/build-a-blockchain/simulate-network/). + +## Template Structure + +A Substrate project such as this consists of a number of components that are spread across a few directories. + +### Node + +A blockchain node is an application that allows users to participate in a blockchain network. +Substrate-based blockchain nodes expose a number of capabilities: + +- Networking: Substrate nodes use the [`libp2p`](https://libp2p.io/) networking stack to allow the + nodes in the network to communicate with one another. +- Consensus: Blockchains must have a way to come to [consensus](https://docs.substrate.io/fundamentals/consensus/) on the state of the network. + Substrate makes it possible to supply custom consensus engines and also ships with several consensus mechanisms that have been built on top of [Web3 Foundation research](https://research.web3.foundation/en/latest/polkadot/NPoS/index.html). +- RPC Server: A remote procedure call (RPC) server is used to interact with Substrate nodes. + +There are several files in the `node` directory. +Take special note of the following: + +- [`chain_spec.rs`](./node/src/chain_spec.rs): A [chain specification](https://docs.substrate.io/build/chain-spec/) is a source code file that defines a Substrate chain's initial (genesis) state. + Chain specifications are useful for development and testing, and critical when architecting the launch of a production chain. + Take note of the `development_config` and `testnet_genesis` functions,. + These functions are used to define the genesis state for the local development chain configuration. + These functions identify some [well-known accounts](https://docs.substrate.io/reference/command-line-tools/subkey/) and use them to configure the blockchain's initial state. +- [`service.rs`](./node/src/service.rs): This file defines the node implementation. + Take note of the libraries that this file imports and the names of the functions it invokes. + In particular, there are references to consensus-related topics, such as the [block finalization and forks](https://docs.substrate.io/fundamentals/consensus/#finalization-and-forks) and other [consensus mechanisms](https://docs.substrate.io/fundamentals/consensus/#default-consensus-models) such as Aura for block authoring and GRANDPA for finality. + + + +### Runtime + +In Substrate, the terms "runtime" and "state transition function" are analogous. +Both terms refer to the core logic of the blockchain that is responsible for validating blocks and executing the state changes they define. +The Substrate project in this repository uses [FRAME](https://docs.substrate.io/learn/runtime-development/#frame) to construct a blockchain runtime. +FRAME allows runtime developers to declare domain-specific logic in modules called "pallets". +At the heart of FRAME is a helpful [macro language](https://docs.substrate.io/reference/frame-macros/) that makes it easy to create pallets and flexibly compose them to create blockchains that can address [a variety of needs](https://substrate.io/ecosystem/projects/). + +Review the [FRAME runtime implementation](./runtime/src/lib.rs) included in this template and note the following: + +- This file configures several pallets to include in the runtime. + Each pallet configuration is defined by a code block that begins with `impl $PALLET_NAME::Config for Runtime`. +- The pallets are composed into a single runtime by way of the [`construct_runtime!`](https://paritytech.github.io/substrate/master/frame_support/macro.construct_runtime.html) macro, which is part of the [core FRAME pallet library](https://docs.substrate.io/reference/frame-pallets/#system-pallets). + +### Pallets + +The runtime in this project is constructed using many FRAME pallets that ship with [the Substrate repository](https://github.com/paritytech/substrate/tree/master/frame) and a template pallet that is [defined in the `pallets`](./pallets/template/src/lib.rs) directory. + +A FRAME pallet is comprised of a number of blockchain primitives, including: + +- Storage: FRAME defines a rich set of powerful [storage abstractions](https://docs.substrate.io/build/runtime-storage/) that makes it easy to use Substrate's efficient key-value database to manage the evolving state of a blockchain. +- Dispatchables: FRAME pallets define special types of functions that can be invoked (dispatched) from outside of the runtime in order to update its state. +- Events: Substrate uses [events](https://docs.substrate.io/build/events-and-errors/) to notify users of significant state changes. +- Errors: When a dispatchable fails, it returns an error. + +Each pallet has its own `Config` trait which serves as a configuration interface to generically define the types and parameters it depends on. + +## Alternatives Installations + +Instead of installing dependencies and building this source directly, consider the following alternatives. + +### Nix + +Install [nix](https://nixos.org/) and +[nix-direnv](https://github.com/nix-community/nix-direnv) for a fully plug-and-play +experience for setting up the development environment. +To get all the correct dependencies, activate direnv `direnv allow`. + +### Docker + +Please follow the [Substrate Docker instructions here](https://github.com/paritytech/substrate/blob/master/docker/README.md) to build the Docker container with the Substrate Node Template binary. diff --git a/node/flake.lock b/node/flake.lock new file mode 100644 index 000000000..60819f675 --- /dev/null +++ b/node/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1678901627, + "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1679262748, + "narHash": "sha256-DQCrrAFrkxijC6haUzOC5ZoFqpcv/tg2WxnyW3np1Cc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "60c1d71f2ba4c80178ec84523c2ca0801522e0a6", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/node/flake.nix b/node/flake.nix new file mode 100644 index 000000000..428efd094 --- /dev/null +++ b/node/flake.nix @@ -0,0 +1,22 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + in + { + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + rustup + clang + protobuf + ]; + + LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; + }; + }); +} diff --git a/node/node-chronicle/Cargo.toml b/node/node-chronicle/Cargo.toml new file mode 100644 index 000000000..bb31c7626 --- /dev/null +++ b/node/node-chronicle/Cargo.toml @@ -0,0 +1,80 @@ +[package] +name = "node-chronicle" +version = "4.0.0-dev" +description = "A fresh FRAME-based Substrate node, ready for hacking." +authors = ["Substrate DevHub "] +homepage = "https://substrate.io/" +edition = "2021" +license = "MIT-0" +publish = false +repository = "https://github.com/substrate-developer-hub/substrate-node-template/" +build = "build.rs" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[[bin]] +name = "node-chronicle" + +[dependencies] +clap = { version = "4.4.2", features = ["derive"] } +futures = { version = "0.3.21", features = ["thread-pool"]} + +sc-cli = { version = "0.32.0" } +sp-core = { version= "24.0.0" } +sc-executor = { version = "0.28.0" } +sc-network = { version = "0.30.0" } +sc-service = { version = "0.31.0" } +sc-telemetry = { version = "11.0.0" } +sc-transaction-pool = { version = "24.0.0" } +sc-transaction-pool-api = { version = "24.0.0" } +sc-offchain = { version = "25.0.0" } +sc-statement-store = { version = "6.0.0" } +sc-consensus-aura = { version = "0.30.0" } +sp-consensus-aura = { version = "0.28.0" } +sc-consensus = { version = "0.29.0" } +sc-consensus-grandpa = { version = "0.15.0" } +sp-consensus-grandpa = { version = "9.0.0" } +sc-client-api = { version = "24.0.0" } +sp-runtime = { version = "27.0.0" } +sp-io = { version = "26.0.0" } +sp-timestamp = { version = "22.0.0" } +sp-inherents = { version = "22.0.0" } +sp-keyring = { version = "27.0.0" } +frame-system = { version = "24.0.0" } +pallet-transaction-payment = { version = "24.0.0", default-features = false } + +# These dependencies are used for the node template's RPCs +jsonrpsee = { version = "0.16.3", features = ["server"] } +sp-api = { version = "22.0.0" } +sc-rpc-api = { version = "0.29.0" } +sp-blockchain = { version = "24.0.0" } +sp-block-builder = { version = "22.0.0" } +sc-basic-authorship = { version = "0.30.0" } +substrate-frame-rpc-system = { version = "24.0.0" } +pallet-transaction-payment-rpc = { version = "26.0.0" } + +# These dependencies are used for runtime benchmarking +frame-benchmarking = { version = "24.0.0" } +frame-benchmarking-cli = { version = "28.0.0" } + +# Local Dependencies +runtime-chronicle = { path = "../runtime-chronicle" } + +# CLI-specific dependencies +try-runtime-cli = { version = "0.34.0", optional = true } + +[build-dependencies] +substrate-build-script-utils = { version = "8.0.0" } + +[features] +default = [] +# Dependencies that are only required if runtime benchmarking should be build. +runtime-benchmarks = [ + "runtime-chronicle/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-benchmarking-cli/runtime-benchmarks", +] +# Enable features that allow the runtime to be tried and debugged. Name might be subject to change +# in the near future. +try-runtime = ["runtime-chronicle/try-runtime", "try-runtime-cli/try-runtime"] diff --git a/node/node-chronicle/build.rs b/node/node-chronicle/build.rs new file mode 100644 index 000000000..e3bfe3116 --- /dev/null +++ b/node/node-chronicle/build.rs @@ -0,0 +1,7 @@ +use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; + +fn main() { + generate_cargo_keys(); + + rerun_if_git_head_changed(); +} diff --git a/node/node-chronicle/src/benchmarking.rs b/node/node-chronicle/src/benchmarking.rs new file mode 100644 index 000000000..848237244 --- /dev/null +++ b/node/node-chronicle/src/benchmarking.rs @@ -0,0 +1,161 @@ +//! Setup code for [`super::command`] which would otherwise bloat that module. +//! +//! Should only be used for benchmarking as it may break in other contexts. + +use crate::service::FullClient; + +use runtime::{AccountId, Balance, BalancesCall, SystemCall}; +use runtime_chronicle as runtime; +use sc_cli::Result; +use sc_client_api::BlockBackend; +use sp_core::{Encode, Pair}; +use sp_inherents::{InherentData, InherentDataProvider}; +use sp_keyring::Sr25519Keyring; +use sp_runtime::{OpaqueExtrinsic, SaturatedConversion}; + +use std::{sync::Arc, time::Duration}; + +/// Generates extrinsics for the `benchmark overhead` command. +/// +/// Note: Should only be used for benchmarking. +pub struct RemarkBuilder { + client: Arc, +} + +impl RemarkBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc) -> Self { + Self { client } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder { + fn pallet(&self) -> &str { + "system" + } + + fn extrinsic(&self) -> &str { + "remark" + } + + fn build(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( + self.client.as_ref(), + acc, + SystemCall::remark { remark: vec![] }.into(), + nonce, + ) + .into(); + + Ok(extrinsic) + } +} + +/// Generates `Balances::TransferKeepAlive` extrinsics for the benchmarks. +/// +/// Note: Should only be used for benchmarking. +pub struct TransferKeepAliveBuilder { + client: Arc, + dest: AccountId, + value: Balance, +} + +impl TransferKeepAliveBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc, dest: AccountId, value: Balance) -> Self { + Self { client, dest, value } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { + fn pallet(&self) -> &str { + "balances" + } + + fn extrinsic(&self) -> &str { + "transfer_keep_alive" + } + + fn build(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( + self.client.as_ref(), + acc, + BalancesCall::transfer_keep_alive { dest: self.dest.clone().into(), value: self.value } + .into(), + nonce, + ) + .into(); + + Ok(extrinsic) + } +} + +/// Create a transaction using the given `call`. +/// +/// Note: Should only be used for benchmarking. +pub fn create_benchmark_extrinsic( + client: &FullClient, + sender: sp_core::sr25519::Pair, + call: runtime::RuntimeCall, + nonce: u32, +) -> runtime::UncheckedExtrinsic { + let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); + let best_hash = client.chain_info().best_hash; + let best_block = client.chain_info().best_number; + + let period = runtime::BlockHashCount::get() + .checked_next_power_of_two() + .map(|c| c / 2) + .unwrap_or(2) as u64; + let extra: runtime::SignedExtra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(sp_runtime::generic::Era::mortal( + period, + best_block.saturated_into(), + )), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ); + + let raw_payload = runtime::SignedPayload::from_raw( + call.clone(), + extra.clone(), + ( + (), + runtime::VERSION.spec_version, + runtime::VERSION.transaction_version, + genesis_hash, + best_hash, + (), + (), + (), + ), + ); + let signature = raw_payload.using_encoded(|e| sender.sign(e)); + + runtime::UncheckedExtrinsic::new_signed( + call, + sp_runtime::AccountId32::from(sender.public()).into(), + runtime::Signature::Sr25519(signature), + extra, + ) +} + +/// Generates inherent data for the `benchmark overhead` command. +/// +/// Note: Should only be used for benchmarking. +pub fn inherent_benchmark_data() -> Result { + let mut inherent_data = InherentData::new(); + let d = Duration::from_millis(0); + let timestamp = sp_timestamp::InherentDataProvider::new(d.into()); + + futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data)) + .map_err(|e| format!("creating inherent data: {:?}", e))?; + Ok(inherent_data) +} diff --git a/node/node-chronicle/src/chain_spec.rs b/node/node-chronicle/src/chain_spec.rs new file mode 100644 index 000000000..608eefdfb --- /dev/null +++ b/node/node-chronicle/src/chain_spec.rs @@ -0,0 +1,158 @@ +use runtime_chronicle::{ + AccountId, AuraConfig, BalancesConfig, GrandpaConfig, RuntimeGenesisConfig, Signature, + SudoConfig, SystemConfig, WASM_BINARY, +}; +use sc_service::ChainType; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_consensus_grandpa::AuthorityId as GrandpaId; +use sp_core::{sr25519, Pair, Public}; +use sp_runtime::traits::{IdentifyAccount, Verify}; + +// The URL for the telemetry server. +// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; + +/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. +pub type ChainSpec = sc_service::GenericChainSpec; + +/// Generate a crypto pair from seed. +pub fn get_from_seed(seed: &str) -> ::Public { + TPublic::Pair::from_string(&format!("//{}", seed), None) + .expect("static values are valid; qed") + .public() +} + +type AccountPublic = ::Signer; + +/// Generate an account ID from seed. +pub fn get_account_id_from_seed(seed: &str) -> AccountId +where + AccountPublic: From<::Public>, +{ + AccountPublic::from(get_from_seed::(seed)).into_account() +} + +/// Generate an Aura authority key. +pub fn authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) { + (get_from_seed::(s), get_from_seed::(s)) +} + +pub fn development_config() -> Result { + let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; + + Ok(ChainSpec::from_genesis( + // Name + "Development", + // ID + "dev", + ChainType::Development, + move || { + testnet_genesis( + wasm_binary, + // Initial PoA authorities + vec![authority_keys_from_seed("Alice")], + // Sudo account + get_account_id_from_seed::("Alice"), + // Pre-funded accounts + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + ], + true, + ) + }, + // Bootnodes + vec![], + // Telemetry + None, + // Protocol ID + None, + None, + // Properties + None, + // Extensions + None, + )) +} + +pub fn local_testnet_config() -> Result { + let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; + + Ok(ChainSpec::from_genesis( + // Name + "Local Testnet", + // ID + "local_testnet", + ChainType::Local, + move || { + testnet_genesis( + wasm_binary, + // Initial PoA authorities + vec![authority_keys_from_seed("Alice"), authority_keys_from_seed("Bob")], + // Sudo account + get_account_id_from_seed::("Alice"), + // Pre-funded accounts + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + true, + ) + }, + // Bootnodes + vec![], + // Telemetry + None, + // Protocol ID + None, + // Properties + None, + None, + // Extensions + None, + )) +} + +/// Configure initial storage state for FRAME modules. +fn testnet_genesis( + wasm_binary: &[u8], + initial_authorities: Vec<(AuraId, GrandpaId)>, + root_key: AccountId, + endowed_accounts: Vec, + _enable_println: bool, +) -> RuntimeGenesisConfig { + RuntimeGenesisConfig { + system: SystemConfig { + // Add Wasm runtime to storage. + code: wasm_binary.to_vec(), + ..Default::default() + }, + balances: BalancesConfig { + // Configure endowed accounts with initial balance of 1 << 60. + balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), + }, + aura: AuraConfig { + authorities: initial_authorities.iter().map(|x| (x.0.clone())).collect(), + }, + grandpa: GrandpaConfig { + authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(), + ..Default::default() + }, + sudo: SudoConfig { + // Assign network admin rights. + key: Some(root_key), + }, + transaction_payment: Default::default(), + } +} diff --git a/node/node-chronicle/src/cli.rs b/node/node-chronicle/src/cli.rs new file mode 100644 index 000000000..15ceaa062 --- /dev/null +++ b/node/node-chronicle/src/cli.rs @@ -0,0 +1,54 @@ +use sc_cli::RunCmd; + +#[derive(Debug, clap::Parser)] +pub struct Cli { + #[command(subcommand)] + pub subcommand: Option, + + #[clap(flatten)] + pub run: RunCmd, +} + +#[derive(Debug, clap::Subcommand)] +#[allow(clippy::large_enum_variant)] +pub enum Subcommand { + /// Key management cli utilities + #[command(subcommand)] + Key(sc_cli::KeySubcommand), + + /// Build a chain specification. + BuildSpec(sc_cli::BuildSpecCmd), + + /// Validate blocks. + CheckBlock(sc_cli::CheckBlockCmd), + + /// Export blocks. + ExportBlocks(sc_cli::ExportBlocksCmd), + + /// Export the state of a given block into a chain spec. + ExportState(sc_cli::ExportStateCmd), + + /// Import blocks. + ImportBlocks(sc_cli::ImportBlocksCmd), + + /// Remove the whole chain. + PurgeChain(sc_cli::PurgeChainCmd), + + /// Revert the chain to a previous state. + Revert(sc_cli::RevertCmd), + + /// Sub-commands concerned with benchmarking. + #[command(subcommand)] + Benchmark(frame_benchmarking_cli::BenchmarkCmd), + + /// Try some command against runtime state. + #[cfg(feature = "try-runtime")] + TryRuntime(try_runtime_cli::TryRuntimeCmd), + + /// Try some command against runtime state. Note: `try-runtime` feature must be enabled. + #[cfg(not(feature = "try-runtime"))] + TryRuntime, + + /// Db meta columns information. + ChainInfo(sc_cli::ChainInfoCmd), +} diff --git a/node/node-chronicle/src/command.rs b/node/node-chronicle/src/command.rs new file mode 100644 index 000000000..cae7ab7b8 --- /dev/null +++ b/node/node-chronicle/src/command.rs @@ -0,0 +1,212 @@ +use crate::{ + benchmarking::{inherent_benchmark_data, RemarkBuilder, TransferKeepAliveBuilder}, + chain_spec, + cli::{Cli, Subcommand}, + service, +}; +use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE}; +use runtime_chronicle::{Block, EXISTENTIAL_DEPOSIT}; +use sc_cli::SubstrateCli; +use sc_service::PartialComponents; +use sp_keyring::Sr25519Keyring; + +#[cfg(feature = "try-runtime")] +use try_runtime_cli::block_building_info::timestamp_with_aura_info; + +impl SubstrateCli for Cli { + fn impl_name() -> String { + "Substrate Node".into() + } + + fn impl_version() -> String { + env!("SUBSTRATE_CLI_IMPL_VERSION").into() + } + + fn description() -> String { + env!("CARGO_PKG_DESCRIPTION").into() + } + + fn author() -> String { + env!("CARGO_PKG_AUTHORS").into() + } + + fn support_url() -> String { + "support.anonymous.an".into() + } + + fn copyright_start_year() -> i32 { + 2017 + } + + fn load_spec(&self, id: &str) -> Result, String> { + Ok(match id { + "dev" => Box::new(chain_spec::development_config()?), + "" | "local" => Box::new(chain_spec::local_testnet_config()?), + path => + Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?), + }) + } +} + +/// Parse and run command line arguments +pub fn run() -> sc_cli::Result<()> { + let cli = Cli::from_args(); + + match &cli.subcommand { + Some(Subcommand::Key(cmd)) => cmd.run(&cli), + Some(Subcommand::BuildSpec(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) + }, + Some(Subcommand::CheckBlock(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, import_queue, .. } = + service::new_partial(&config)?; + Ok((cmd.run(client, import_queue), task_manager)) + }) + }, + Some(Subcommand::ExportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; + Ok((cmd.run(client, config.database), task_manager)) + }) + }, + Some(Subcommand::ExportState(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; + Ok((cmd.run(client, config.chain_spec), task_manager)) + }) + }, + Some(Subcommand::ImportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, import_queue, .. } = + service::new_partial(&config)?; + Ok((cmd.run(client, import_queue), task_manager)) + }) + }, + Some(Subcommand::PurgeChain(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.database)) + }, + Some(Subcommand::Revert(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, backend, .. } = + service::new_partial(&config)?; + let aux_revert = Box::new(|client, _, blocks| { + sc_consensus_grandpa::revert(client, blocks)?; + Ok(()) + }); + Ok((cmd.run(client, backend, Some(aux_revert)), task_manager)) + }) + }, + Some(Subcommand::Benchmark(cmd)) => { + let runner = cli.create_runner(cmd)?; + + runner.sync_run(|config| { + // This switch needs to be in the client, since the client decides + // which sub-commands it wants to support. + match cmd { + BenchmarkCmd::Pallet(cmd) => { + if !cfg!(feature = "runtime-benchmarks") { + return Err( + "Runtime benchmarking wasn't enabled when building the node. \ + You can enable it with `--features runtime-benchmarks`." + .into(), + ) + } + + cmd.run::(config) + }, + BenchmarkCmd::Block(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; + cmd.run(client) + }, + #[cfg(not(feature = "runtime-benchmarks"))] + BenchmarkCmd::Storage(_) => Err( + "Storage benchmarking can be enabled with `--features runtime-benchmarks`." + .into(), + ), + #[cfg(feature = "runtime-benchmarks")] + BenchmarkCmd::Storage(cmd) => { + let PartialComponents { client, backend, .. } = + service::new_partial(&config)?; + let db = backend.expose_db(); + let storage = backend.expose_storage(); + + cmd.run(config, client, db, storage) + }, + BenchmarkCmd::Overhead(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; + let ext_builder = RemarkBuilder::new(client.clone()); + + cmd.run( + config, + client, + inherent_benchmark_data()?, + Vec::new(), + &ext_builder, + ) + }, + BenchmarkCmd::Extrinsic(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; + // Register the *Remark* and *TKA* builders. + let ext_factory = ExtrinsicFactory(vec![ + Box::new(RemarkBuilder::new(client.clone())), + Box::new(TransferKeepAliveBuilder::new( + client.clone(), + Sr25519Keyring::Alice.to_account_id(), + EXISTENTIAL_DEPOSIT, + )), + ]); + + cmd.run(client, inherent_benchmark_data()?, Vec::new(), &ext_factory) + }, + BenchmarkCmd::Machine(cmd) => + cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()), + } + }) + }, + #[cfg(feature = "try-runtime")] + Some(Subcommand::TryRuntime(cmd)) => { + use crate::service::ExecutorDispatch; + use sc_executor::{sp_wasm_interface::ExtendedHostFunctions, NativeExecutionDispatch}; + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + // we don't need any of the components of new_partial, just a runtime, or a task + // manager to do `async_run`. + let registry = config.prometheus_config.as_ref().map(|cfg| &cfg.registry); + let task_manager = + sc_service::TaskManager::new(config.tokio_handle.clone(), registry) + .map_err(|e| sc_cli::Error::Service(sc_service::Error::Prometheus(e)))?; + let info_provider = timestamp_with_aura_info(6000); + + Ok(( + cmd.run::::ExtendHostFunctions, + >, _>(Some(info_provider)), + task_manager, + )) + }) + }, + #[cfg(not(feature = "try-runtime"))] + Some(Subcommand::TryRuntime) => Err("TryRuntime wasn't enabled when building the node. \ + You can enable it with `--features try-runtime`." + .into()), + Some(Subcommand::ChainInfo(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run::(&config)) + }, + None => { + let runner = cli.create_runner(&cli.run)?; + runner.run_node_until_exit(|config| async move { + service::new_full(config).map_err(sc_cli::Error::Service) + }) + }, + } +} diff --git a/node/node-chronicle/src/lib.rs b/node/node-chronicle/src/lib.rs new file mode 100644 index 000000000..f117b8aae --- /dev/null +++ b/node/node-chronicle/src/lib.rs @@ -0,0 +1,3 @@ +pub mod chain_spec; +pub mod rpc; +pub mod service; diff --git a/node/node-chronicle/src/main.rs b/node/node-chronicle/src/main.rs new file mode 100644 index 000000000..426cbabb6 --- /dev/null +++ b/node/node-chronicle/src/main.rs @@ -0,0 +1,14 @@ +//! Substrate Node Template CLI library. +#![warn(missing_docs)] + +mod chain_spec; +#[macro_use] +mod service; +mod benchmarking; +mod cli; +mod command; +mod rpc; + +fn main() -> sc_cli::Result<()> { + command::run() +} diff --git a/node/node-chronicle/src/rpc.rs b/node/node-chronicle/src/rpc.rs new file mode 100644 index 000000000..00a2c4a1a --- /dev/null +++ b/node/node-chronicle/src/rpc.rs @@ -0,0 +1,57 @@ +//! A collection of node-specific RPC methods. +//! Substrate provides the `sc-rpc` crate, which defines the core RPC layer +//! used by Substrate nodes. This file extends those RPC definitions with +//! capabilities that are specific to this project's runtime configuration. + +#![warn(missing_docs)] + +use std::sync::Arc; + +use jsonrpsee::RpcModule; +use runtime_chronicle::{opaque::Block, AccountId, Balance, Nonce}; +use sc_transaction_pool_api::TransactionPool; +use sp_api::ProvideRuntimeApi; +use sp_block_builder::BlockBuilder; +use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; + +pub use sc_rpc_api::DenyUnsafe; + +/// Full client dependencies. +pub struct FullDeps { + /// The client instance to use. + pub client: Arc, + /// Transaction pool instance. + pub pool: Arc

, + /// Whether to deny unsafe calls + pub deny_unsafe: DenyUnsafe, +} + +/// Instantiate all full RPC extensions. +pub fn create_full( + deps: FullDeps, +) -> Result, Box> +where + C: ProvideRuntimeApi, + C: HeaderBackend + HeaderMetadata + 'static, + C: Send + Sync + 'static, + C::Api: substrate_frame_rpc_system::AccountNonceApi, + C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, + C::Api: BlockBuilder, + P: TransactionPool + 'static, +{ + use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; + use substrate_frame_rpc_system::{System, SystemApiServer}; + + let mut module = RpcModule::new(()); + let FullDeps { client, pool, deny_unsafe } = deps; + + module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; + module.merge(TransactionPayment::new(client).into_rpc())?; + + // Extend this RPC with a custom API by using the following syntax. + // `YourRpcStruct` should have a reference to a client, which is needed + // to call into the runtime. + //module.merge(YourRpcTrait::into_rpc(YourRpcStruct::new(ReferenceToClient, ...)))?;` + + Ok(module) +} diff --git a/node/node-chronicle/src/service.rs b/node/node-chronicle/src/service.rs new file mode 100644 index 000000000..8eaa302a2 --- /dev/null +++ b/node/node-chronicle/src/service.rs @@ -0,0 +1,335 @@ +//! Service and ServiceFactory implementation. Specialized wrapper over substrate service. + +use futures::FutureExt; +use runtime_chronicle::{self, opaque::Block, RuntimeApi}; +use sc_client_api::{Backend, BlockBackend}; +use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams}; +use sc_consensus_grandpa::SharedVoterState; +pub use sc_executor::NativeElseWasmExecutor; +use sc_service::{error::Error as ServiceError, Configuration, TaskManager, WarpSyncParams}; +use sc_telemetry::{Telemetry, TelemetryWorker}; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; +use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; +use std::{sync::Arc, time::Duration}; + +// Our native executor instance. +pub struct ExecutorDispatch; + +impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { + /// Only enable the benchmarking host functions when we actually want to benchmark. + #[cfg(feature = "runtime-benchmarks")] + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + /// Otherwise we only use the default Substrate host functions. + #[cfg(not(feature = "runtime-benchmarks"))] + type ExtendHostFunctions = (); + + fn dispatch(method: &str, data: &[u8]) -> Option> { + runtime_chronicle::api::dispatch(method, data) + } + + fn native_version() -> sc_executor::NativeVersion { + runtime_chronicle::native_version() + } +} + +pub(crate) type FullClient = + sc_service::TFullClient>; +type FullBackend = sc_service::TFullBackend; +type FullSelectChain = sc_consensus::LongestChain; + +const JUSTIFICATION_IMPORT_PERIOD: u32 = 32; + +#[allow(clippy::type_complexity)] +pub fn new_partial( + config: &Configuration, +) -> Result< + sc_service::PartialComponents< + FullClient, + FullBackend, + FullSelectChain, + sc_consensus::DefaultImportQueue, + sc_transaction_pool::FullPool, + ( + sc_consensus_grandpa::GrandpaBlockImport< + FullBackend, + Block, + FullClient, + FullSelectChain, + >, + sc_consensus_grandpa::LinkHalf, + Option, + ), + >, + ServiceError, +> { + let telemetry = config + .telemetry_endpoints + .clone() + .filter(|x| !x.is_empty()) + .map(|endpoints| -> Result<_, sc_telemetry::Error> { + let worker = TelemetryWorker::new(16)?; + let telemetry = worker.handle().new_telemetry(endpoints); + Ok((worker, telemetry)) + }) + .transpose()?; + + let executor = sc_service::new_native_or_wasm_executor(config); + let (client, backend, keystore_container, task_manager) = + sc_service::new_full_parts::( + config, + telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), + executor, + )?; + let client = Arc::new(client); + + let telemetry = telemetry.map(|(worker, telemetry)| { + task_manager.spawn_handle().spawn("telemetry", None, worker.run()); + telemetry + }); + + let select_chain = sc_consensus::LongestChain::new(backend.clone()); + + let transaction_pool = sc_transaction_pool::BasicPool::new_full( + config.transaction_pool.clone(), + config.role.is_authority().into(), + config.prometheus_registry(), + task_manager.spawn_essential_handle(), + client.clone(), + ); + + let (grandpa_block_import, grandpa_link) = sc_consensus_grandpa::block_import( + client.clone(), + JUSTIFICATION_IMPORT_PERIOD, + &client, + select_chain.clone(), + telemetry.as_ref().map(|x| x.handle()), + )?; + + let slot_duration = sc_consensus_aura::slot_duration(&*client)?; + + let import_queue = + sc_consensus_aura::import_queue::(ImportQueueParams { + block_import: grandpa_block_import.clone(), + justification_import: Some(Box::new(grandpa_block_import.clone())), + client: client.clone(), + create_inherent_data_providers: move |_, ()| async move { + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *timestamp, + slot_duration, + ); + + Ok((slot, timestamp)) + }, + spawner: &task_manager.spawn_essential_handle(), + registry: config.prometheus_registry(), + check_for_equivocation: Default::default(), + telemetry: telemetry.as_ref().map(|x| x.handle()), + compatibility_mode: Default::default(), + })?; + + Ok(sc_service::PartialComponents { + client, + backend, + task_manager, + import_queue, + keystore_container, + select_chain, + transaction_pool, + other: (grandpa_block_import, grandpa_link, telemetry), + }) +} + +/// Builds a new service for a full client. +pub fn new_full(config: Configuration) -> Result { + let sc_service::PartialComponents { + client, + backend, + mut task_manager, + import_queue, + keystore_container, + select_chain, + transaction_pool, + other: (block_import, grandpa_link, mut telemetry), + } = new_partial(&config)?; + + let mut net_config = sc_network::config::FullNetworkConfiguration::new(&config.network); + + let grandpa_protocol_name = sc_consensus_grandpa::protocol_standard_name( + &client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"), + &config.chain_spec, + ); + net_config.add_notification_protocol(sc_consensus_grandpa::grandpa_peers_set_config( + grandpa_protocol_name.clone(), + )); + + let warp_sync = Arc::new(sc_consensus_grandpa::warp_proof::NetworkProvider::new( + backend.clone(), + grandpa_link.shared_authority_set().clone(), + Vec::default(), + )); + + let (network, system_rpc_tx, tx_handler_controller, network_starter, sync_service) = + sc_service::build_network(sc_service::BuildNetworkParams { + config: &config, + net_config, + client: client.clone(), + transaction_pool: transaction_pool.clone(), + spawn_handle: task_manager.spawn_handle(), + import_queue, + block_relay: None, + block_announce_validator_builder: None, + warp_sync_params: Some(WarpSyncParams::WithProvider(warp_sync)), + })?; + + if config.offchain_worker.enabled { + task_manager.spawn_handle().spawn( + "offchain-workers-runner", + "offchain-worker", + sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions { + runtime_api_provider: client.clone(), + is_validator: config.role.is_authority(), + keystore: Some(keystore_container.keystore()), + offchain_db: backend.offchain_storage(), + transaction_pool: Some(OffchainTransactionPoolFactory::new( + transaction_pool.clone(), + )), + network_provider: network.clone(), + enable_http_requests: true, + custom_extensions: |_| vec![], + }) + .run(client.clone(), task_manager.spawn_handle()) + .boxed(), + ); + } + + let role = config.role.clone(); + let force_authoring = config.force_authoring; + let backoff_authoring_blocks: Option<()> = None; + let name = config.network.node_name.clone(); + let enable_grandpa = !config.disable_grandpa; + let prometheus_registry = config.prometheus_registry().cloned(); + + let rpc_extensions_builder = { + let client = client.clone(); + let pool = transaction_pool.clone(); + + Box::new(move |deny_unsafe, _| { + let deps = + crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; + crate::rpc::create_full(deps).map_err(Into::into) + }) + }; + + let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { + network: network.clone(), + client: client.clone(), + keystore: keystore_container.keystore(), + task_manager: &mut task_manager, + transaction_pool: transaction_pool.clone(), + rpc_builder: rpc_extensions_builder, + backend, + system_rpc_tx, + tx_handler_controller, + sync_service: sync_service.clone(), + config, + telemetry: telemetry.as_mut(), + })?; + + if role.is_authority() { + let proposer_factory = sc_basic_authorship::ProposerFactory::new( + task_manager.spawn_handle(), + client.clone(), + transaction_pool.clone(), + prometheus_registry.as_ref(), + telemetry.as_ref().map(|x| x.handle()), + ); + + let slot_duration = sc_consensus_aura::slot_duration(&*client)?; + + let aura = sc_consensus_aura::start_aura::( + StartAuraParams { + slot_duration, + client, + select_chain, + block_import, + proposer_factory, + create_inherent_data_providers: move |_, ()| async move { + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + *timestamp, + slot_duration, + ); + + Ok((slot, timestamp)) + }, + force_authoring, + backoff_authoring_blocks, + keystore: keystore_container.keystore(), + sync_oracle: sync_service.clone(), + justification_sync_link: sync_service.clone(), + block_proposal_slot_portion: SlotProportion::new(2f32 / 3f32), + max_block_proposal_slot_portion: None, + telemetry: telemetry.as_ref().map(|x| x.handle()), + compatibility_mode: Default::default(), + }, + )?; + + // the AURA authoring task is considered essential, i.e. if it + // fails we take down the service with it. + task_manager + .spawn_essential_handle() + .spawn_blocking("aura", Some("block-authoring"), aura); + } + + if enable_grandpa { + // if the node isn't actively participating in consensus then it doesn't + // need a keystore, regardless of which protocol we use below. + let keystore = if role.is_authority() { Some(keystore_container.keystore()) } else { None }; + + let grandpa_config = sc_consensus_grandpa::Config { + // FIXME #1578 make this available through chainspec + gossip_duration: Duration::from_millis(333), + justification_generation_period: 512, + name: Some(name), + observer_enabled: false, + keystore, + local_role: role, + telemetry: telemetry.as_ref().map(|x| x.handle()), + protocol_name: grandpa_protocol_name, + }; + + // start the full GRANDPA voter + // NOTE: non-authorities could run the GRANDPA observer protocol, but at + // this point the full voter should provide better guarantees of block + // and vote data availability than the observer. The observer has not + // been tested extensively yet and having most nodes in a network run it + // could lead to finality stalls. + let grandpa_config = sc_consensus_grandpa::GrandpaParams { + config: grandpa_config, + link: grandpa_link, + network, + sync: Arc::new(sync_service), + voting_rule: sc_consensus_grandpa::VotingRulesBuilder::default().build(), + prometheus_registry, + shared_voter_state: SharedVoterState::empty(), + telemetry: telemetry.as_ref().map(|x| x.handle()), + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(transaction_pool), + }; + + // the GRANDPA voter task is considered infallible, i.e. + // if it fails we take down the service with it. + task_manager.spawn_essential_handle().spawn_blocking( + "grandpa-voter", + None, + sc_consensus_grandpa::run_grandpa_voter(grandpa_config)?, + ); + } + + network_starter.start_network(); + Ok(task_manager) +} diff --git a/node/node-chronicle/tree.txt b/node/node-chronicle/tree.txt new file mode 100644 index 000000000..4d6cf6c61 --- /dev/null +++ b/node/node-chronicle/tree.txt @@ -0,0 +1,3436 @@ +node-chronicle v4.0.0-dev (/Users/ryan/code/chronicle/node/node-chronicle) +├── clap v4.4.7 +│ ├── clap_builder v4.4.7 +│ │ ├── anstream v0.6.4 +│ │ │ ├── anstyle v1.0.4 +│ │ │ ├── anstyle-parse v0.2.2 +│ │ │ │ └── utf8parse v0.2.1 +│ │ │ ├── anstyle-query v1.0.0 +│ │ │ ├── colorchoice v1.0.0 +│ │ │ └── utf8parse v0.2.1 +│ │ ├── anstyle v1.0.4 +│ │ ├── clap_lex v0.6.0 +│ │ └── strsim v0.10.0 +│ └── clap_derive v4.4.7 (proc-macro) +│ ├── heck v0.4.1 +│ ├── proc-macro2 v1.0.69 +│ │ └── unicode-ident v1.0.12 +│ ├── quote v1.0.33 +│ │ └── proc-macro2 v1.0.69 (*) +│ └── syn v2.0.38 +│ ├── proc-macro2 v1.0.69 (*) +│ ├── quote v1.0.33 (*) +│ └── unicode-ident v1.0.12 +├── frame-benchmarking v24.0.0 +│ ├── frame-support v24.0.0 +│ │ ├── aquamarine v0.3.2 (proc-macro) +│ │ │ ├── include_dir v0.7.3 +│ │ │ │ └── include_dir_macros v0.7.3 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ └── quote v1.0.33 (*) +│ │ │ ├── itertools v0.10.5 +│ │ │ │ └── either v1.9.0 +│ │ │ ├── proc-macro-error v1.0.4 +│ │ │ │ ├── proc-macro-error-attr v1.0.4 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ └── quote v1.0.33 (*) +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── version_check v0.9.4 +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v1.0.109 +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── unicode-ident v1.0.12 +│ │ │ │ [build-dependencies] +│ │ │ │ └── version_check v0.9.4 +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ ├── bitflags v1.3.2 +│ │ ├── docify v0.2.4 +│ │ │ └── docify_macros v0.2.4 (proc-macro) +│ │ │ ├── common-path v1.0.0 +│ │ │ ├── derive-syn-parse v0.1.5 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v1.0.109 (*) +│ │ │ ├── once_cell v1.18.0 +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ ├── regex v1.10.2 +│ │ │ │ ├── aho-corasick v1.1.2 +│ │ │ │ │ └── memchr v2.6.4 +│ │ │ │ ├── memchr v2.6.4 +│ │ │ │ ├── regex-automata v0.4.3 +│ │ │ │ │ ├── aho-corasick v1.1.2 (*) +│ │ │ │ │ ├── memchr v2.6.4 +│ │ │ │ │ └── regex-syntax v0.8.2 +│ │ │ │ └── regex-syntax v0.8.2 +│ │ │ ├── syn v2.0.38 (*) +│ │ │ ├── termcolor v1.3.0 +│ │ │ ├── toml v0.7.8 +│ │ │ │ ├── serde v1.0.189 +│ │ │ │ │ └── serde_derive v1.0.189 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── serde_spanned v0.6.4 +│ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ ├── toml_datetime v0.6.5 +│ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ └── toml_edit v0.19.15 +│ │ │ │ ├── indexmap v2.0.2 +│ │ │ │ │ ├── equivalent v1.0.1 +│ │ │ │ │ └── hashbrown v0.14.2 +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── serde_spanned v0.6.4 (*) +│ │ │ │ ├── toml_datetime v0.6.5 (*) +│ │ │ │ └── winnow v0.5.17 +│ │ │ └── walkdir v2.4.0 +│ │ │ └── same-file v1.0.6 +│ │ ├── environmental v1.1.4 +│ │ ├── frame-metadata v16.0.0 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── parity-scale-codec v3.6.5 +│ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ ├── byte-slice-cast v1.2.2 +│ │ │ │ ├── bytes v1.5.0 +│ │ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ ├── parity-scale-codec-derive v3.6.5 (proc-macro) +│ │ │ │ │ ├── proc-macro-crate v1.1.3 +│ │ │ │ │ │ ├── thiserror v1.0.50 +│ │ │ │ │ │ │ └── thiserror-impl v1.0.50 (proc-macro) +│ │ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ │ └── toml v0.5.11 +│ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ └── serde v1.0.189 +│ │ │ │ └── serde_derive v1.0.189 (proc-macro) (*) +│ │ │ ├── scale-info v2.10.0 +│ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ ├── derive_more v0.99.17 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── scale-info-derive v2.10.0 (proc-macro) +│ │ │ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ └── serde v1.0.189 (*) +│ │ │ └── serde v1.0.189 (*) +│ │ ├── frame-support-procedural v19.0.0 (proc-macro) +│ │ │ ├── Inflector v0.11.4 +│ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ └── regex v1.10.2 (*) +│ │ │ ├── cfg-expr v0.15.5 +│ │ │ │ └── smallvec v1.11.1 +│ │ │ ├── derive-syn-parse v0.1.5 (proc-macro) (*) +│ │ │ ├── expander v2.0.0 +│ │ │ │ ├── blake2 v0.10.6 +│ │ │ │ │ └── digest v0.10.7 +│ │ │ │ │ ├── block-buffer v0.10.4 +│ │ │ │ │ │ └── generic-array v0.14.7 +│ │ │ │ │ │ └── typenum v1.17.0 +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── version_check v0.9.4 +│ │ │ │ │ ├── crypto-common v0.1.6 +│ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ └── typenum v1.17.0 +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ ├── fs-err v2.9.0 +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ ├── frame-support-procedural-tools v8.0.0 +│ │ │ │ ├── frame-support-procedural-tools-derive v9.0.0 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ ├── itertools v0.10.5 (*) +│ │ │ ├── macro_magic v0.4.2 +│ │ │ │ ├── macro_magic_core v0.4.2 +│ │ │ │ │ ├── const-random v0.1.16 +│ │ │ │ │ │ └── const-random-macro v0.1.16 (proc-macro) +│ │ │ │ │ │ ├── getrandom v0.2.10 +│ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ │ └── tiny-keccak v2.0.2 +│ │ │ │ │ │ └── crunchy v0.2.2 +│ │ │ │ │ ├── derive-syn-parse v0.1.5 (proc-macro) (*) +│ │ │ │ │ ├── macro_magic_core_macros v0.4.3 (proc-macro) +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── macro_magic_macros v0.4.2 (proc-macro) +│ │ │ │ │ ├── macro_magic_core v0.4.2 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ ├── proc-macro-warning v0.4.2 +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v2.0.38 (*) +│ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ ├── k256 v0.13.1 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── ecdsa v0.16.8 +│ │ │ │ ├── der v0.7.8 +│ │ │ │ │ ├── const-oid v0.9.5 +│ │ │ │ │ └── zeroize v1.6.0 +│ │ │ │ │ └── zeroize_derive v1.4.2 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── digest v0.10.7 +│ │ │ │ │ ├── block-buffer v0.10.4 (*) +│ │ │ │ │ ├── const-oid v0.9.5 +│ │ │ │ │ ├── crypto-common v0.1.6 +│ │ │ │ │ │ ├── generic-array v0.14.7 +│ │ │ │ │ │ │ ├── typenum v1.17.0 +│ │ │ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── version_check v0.9.4 +│ │ │ │ │ │ ├── rand_core v0.6.4 +│ │ │ │ │ │ │ └── getrandom v0.2.10 +│ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ └── typenum v1.17.0 +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ ├── elliptic-curve v0.13.6 +│ │ │ │ │ ├── base16ct v0.2.0 +│ │ │ │ │ ├── crypto-bigint v0.5.3 +│ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ ├── ff v0.13.0 +│ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ ├── group v0.13.0 +│ │ │ │ │ │ ├── ff v0.13.0 (*) +│ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ ├── sec1 v0.7.3 +│ │ │ │ │ │ ├── base16ct v0.2.0 +│ │ │ │ │ │ ├── der v0.7.8 (*) +│ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── rfc6979 v0.4.0 +│ │ │ │ │ ├── hmac v0.12.1 +│ │ │ │ │ │ └── digest v0.10.7 (*) +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ ├── signature v2.1.0 +│ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ └── rand_core v0.6.4 (*) +│ │ │ │ └── spki v0.7.2 +│ │ │ │ └── der v0.7.8 (*) +│ │ │ ├── elliptic-curve v0.13.6 (*) +│ │ │ └── sha2 v0.10.8 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── cpufeatures v0.2.10 +│ │ │ │ └── libc v0.2.149 +│ │ │ └── digest v0.10.7 (*) +│ │ ├── log v0.4.20 +│ │ ├── macro_magic v0.4.2 +│ │ │ └── macro_magic_macros v0.4.2 (proc-macro) (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── paste v1.0.14 (proc-macro) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── serde v1.0.189 (*) +│ │ ├── serde_json v1.0.107 +│ │ │ ├── itoa v1.0.9 +│ │ │ ├── ryu v1.0.15 +│ │ │ └── serde v1.0.189 (*) +│ │ ├── smallvec v1.11.1 +│ │ ├── sp-api v22.0.0 +│ │ │ ├── hash-db v0.16.0 +│ │ │ ├── log v0.4.20 +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ ├── sp-api-proc-macro v11.0.0 (proc-macro) +│ │ │ │ ├── Inflector v0.11.4 (*) +│ │ │ │ ├── blake2 v0.10.6 (*) +│ │ │ │ ├── expander v2.0.0 (*) +│ │ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ ├── sp-core v24.0.0 +│ │ │ │ ├── array-bytes v6.1.0 +│ │ │ │ ├── bitflags v1.3.2 +│ │ │ │ ├── blake2 v0.10.6 (*) +│ │ │ │ ├── bounded-collections v0.1.9 +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ ├── bs58 v0.5.0 +│ │ │ │ ├── dyn-clonable v0.9.0 +│ │ │ │ │ ├── dyn-clonable-impl v0.9.0 (proc-macro) +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ │ └── dyn-clone v1.0.14 +│ │ │ │ ├── ed25519-zebra v3.1.0 +│ │ │ │ │ ├── curve25519-dalek v3.2.0 +│ │ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ │ ├── digest v0.9.0 +│ │ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ │ ├── rand_core v0.5.1 +│ │ │ │ │ │ │ └── getrandom v0.1.16 +│ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ │ ├── hashbrown v0.12.3 +│ │ │ │ │ │ └── ahash v0.7.7 +│ │ │ │ │ │ ├── getrandom v0.2.10 (*) +│ │ │ │ │ │ └── once_cell v1.18.0 +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── version_check v0.9.4 +│ │ │ │ │ ├── hex v0.4.3 +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ ├── sha2 v0.9.9 +│ │ │ │ │ │ ├── block-buffer v0.9.0 +│ │ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ ├── cpufeatures v0.2.10 (*) +│ │ │ │ │ │ ├── digest v0.9.0 (*) +│ │ │ │ │ │ └── opaque-debug v0.3.0 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── futures v0.3.28 +│ │ │ │ │ ├── futures-channel v0.3.28 +│ │ │ │ │ │ ├── futures-core v0.3.28 +│ │ │ │ │ │ └── futures-sink v0.3.28 +│ │ │ │ │ ├── futures-core v0.3.28 +│ │ │ │ │ ├── futures-executor v0.3.28 +│ │ │ │ │ │ ├── futures-core v0.3.28 +│ │ │ │ │ │ ├── futures-task v0.3.28 +│ │ │ │ │ │ ├── futures-util v0.3.28 +│ │ │ │ │ │ │ ├── futures-channel v0.3.28 (*) +│ │ │ │ │ │ │ ├── futures-core v0.3.28 +│ │ │ │ │ │ │ ├── futures-io v0.3.28 +│ │ │ │ │ │ │ ├── futures-macro v0.3.28 (proc-macro) +│ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ │ │ ├── futures-sink v0.3.28 +│ │ │ │ │ │ │ ├── futures-task v0.3.28 +│ │ │ │ │ │ │ ├── memchr v2.6.4 +│ │ │ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ │ │ ├── pin-utils v0.1.0 +│ │ │ │ │ │ │ └── slab v0.4.9 +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── autocfg v1.1.0 +│ │ │ │ │ │ └── num_cpus v1.16.0 +│ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ ├── futures-io v0.3.28 +│ │ │ │ │ ├── futures-sink v0.3.28 +│ │ │ │ │ ├── futures-task v0.3.28 +│ │ │ │ │ └── futures-util v0.3.28 (*) +│ │ │ │ ├── hash-db v0.16.0 +│ │ │ │ ├── hash256-std-hasher v0.15.2 +│ │ │ │ │ └── crunchy v0.2.2 +│ │ │ │ ├── impl-serde v0.4.0 +│ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ ├── libsecp256k1 v0.7.1 +│ │ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ │ ├── base64 v0.13.1 +│ │ │ │ │ ├── digest v0.9.0 (*) +│ │ │ │ │ ├── hmac-drbg v0.3.0 +│ │ │ │ │ │ ├── digest v0.9.0 (*) +│ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ └── hmac v0.8.1 +│ │ │ │ │ │ ├── crypto-mac v0.8.0 +│ │ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ │ └── digest v0.9.0 (*) +│ │ │ │ │ ├── libsecp256k1-core v0.3.0 +│ │ │ │ │ │ ├── crunchy v0.2.2 +│ │ │ │ │ │ ├── digest v0.9.0 (*) +│ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ ├── rand v0.8.5 +│ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ ├── rand_chacha v0.3.1 +│ │ │ │ │ │ │ ├── ppv-lite86 v0.2.17 +│ │ │ │ │ │ │ └── rand_core v0.6.4 (*) +│ │ │ │ │ │ └── rand_core v0.6.4 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── sha2 v0.9.9 (*) +│ │ │ │ │ └── typenum v1.17.0 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ ├── libsecp256k1-gen-ecmult v0.3.0 +│ │ │ │ │ │ └── libsecp256k1-core v0.3.0 +│ │ │ │ │ │ ├── crunchy v0.2.2 +│ │ │ │ │ │ ├── digest v0.9.0 +│ │ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ └── libsecp256k1-gen-genmult v0.3.0 +│ │ │ │ │ └── libsecp256k1-core v0.3.0 (*) +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── merlin v2.0.1 +│ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ ├── keccak v0.1.4 +│ │ │ │ │ │ └── cpufeatures v0.2.10 (*) +│ │ │ │ │ ├── rand_core v0.5.1 (*) +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── parking_lot v0.12.1 +│ │ │ │ │ ├── lock_api v0.4.11 +│ │ │ │ │ │ └── scopeguard v1.2.0 +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── autocfg v1.1.0 +│ │ │ │ │ └── parking_lot_core v0.9.9 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ │ ├── primitive-types v0.12.2 +│ │ │ │ │ ├── fixed-hash v0.8.0 +│ │ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ ├── rustc-hex v2.1.0 +│ │ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ │ ├── impl-codec v0.6.0 +│ │ │ │ │ │ └── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── impl-serde v0.4.0 (*) +│ │ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ │ └── uint v0.9.5 +│ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ ├── crunchy v0.2.2 +│ │ │ │ │ ├── hex v0.4.3 +│ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ ├── regex v1.10.2 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ ├── schnorrkel v0.9.1 +│ │ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ │ ├── arrayvec v0.5.2 +│ │ │ │ │ ├── curve25519-dalek v2.1.3 +│ │ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ │ ├── digest v0.8.1 +│ │ │ │ │ │ │ └── generic-array v0.12.4 +│ │ │ │ │ │ │ └── typenum v1.17.0 +│ │ │ │ │ │ ├── rand_core v0.5.1 (*) +│ │ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ │ ├── getrandom v0.1.16 (*) +│ │ │ │ │ ├── merlin v2.0.1 (*) +│ │ │ │ │ ├── rand v0.7.3 +│ │ │ │ │ │ ├── getrandom v0.1.16 (*) +│ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ ├── rand_chacha v0.2.2 +│ │ │ │ │ │ │ ├── ppv-lite86 v0.2.17 +│ │ │ │ │ │ │ └── rand_core v0.5.1 (*) +│ │ │ │ │ │ └── rand_core v0.5.1 (*) +│ │ │ │ │ ├── rand_core v0.5.1 (*) +│ │ │ │ │ ├── sha2 v0.8.2 +│ │ │ │ │ │ ├── block-buffer v0.7.3 +│ │ │ │ │ │ │ ├── block-padding v0.1.5 +│ │ │ │ │ │ │ │ └── byte-tools v0.3.1 +│ │ │ │ │ │ │ ├── byte-tools v0.3.1 +│ │ │ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ │ │ └── generic-array v0.12.4 (*) +│ │ │ │ │ │ ├── digest v0.8.1 (*) +│ │ │ │ │ │ ├── fake-simd v0.1.2 +│ │ │ │ │ │ └── opaque-debug v0.2.3 +│ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── secp256k1 v0.24.3 +│ │ │ │ │ └── secp256k1-sys v0.6.1 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── cc v1.0.83 +│ │ │ │ │ ├── jobserver v0.1.27 +│ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ └── libc v0.2.149 +│ │ │ │ ├── secrecy v0.8.0 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── sp-core-hashing v12.0.0 +│ │ │ │ │ ├── blake2b_simd v1.0.2 +│ │ │ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ │ │ └── constant_time_eq v0.3.0 +│ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ ├── sha3 v0.10.8 +│ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ └── keccak v0.1.4 (*) +│ │ │ │ │ └── twox-hash v1.6.3 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ ├── sp-debug-derive v11.0.0 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── sp-externalities v0.22.0 +│ │ │ │ │ ├── environmental v1.1.4 +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ └── sp-storage v16.0.0 +│ │ │ │ │ ├── impl-serde v0.4.0 (*) +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── ref-cast v1.0.20 +│ │ │ │ │ │ └── ref-cast-impl v1.0.20 (proc-macro) +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── sp-debug-derive v11.0.0 (proc-macro) (*) +│ │ │ │ │ └── sp-std v11.0.0 +│ │ │ │ ├── sp-runtime-interface v20.0.0 +│ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── primitive-types v0.12.2 (*) +│ │ │ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ │ │ ├── sp-runtime-interface-proc-macro v14.0.0 (proc-macro) +│ │ │ │ │ │ ├── Inflector v0.11.4 (*) +│ │ │ │ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ ├── sp-storage v16.0.0 (*) +│ │ │ │ │ ├── sp-tracing v13.0.0 +│ │ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ │ ├── tracing v0.1.40 +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ │ │ ├── tracing-attributes v0.1.27 (proc-macro) +│ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ │ │ └── tracing-core v0.1.32 +│ │ │ │ │ │ │ └── once_cell v1.18.0 +│ │ │ │ │ │ ├── tracing-core v0.1.32 (*) +│ │ │ │ │ │ └── tracing-subscriber v0.2.25 +│ │ │ │ │ │ ├── ansi_term v0.12.1 +│ │ │ │ │ │ ├── chrono v0.4.31 +│ │ │ │ │ │ │ ├── iana-time-zone v0.1.58 +│ │ │ │ │ │ │ │ └── core-foundation-sys v0.8.4 +│ │ │ │ │ │ │ ├── num-traits v0.2.17 +│ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ └── autocfg v1.1.0 +│ │ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ │ ├── matchers v0.0.1 +│ │ │ │ │ │ │ └── regex-automata v0.1.10 +│ │ │ │ │ │ │ └── regex-syntax v0.6.29 +│ │ │ │ │ │ ├── parking_lot v0.11.2 +│ │ │ │ │ │ │ ├── instant v0.1.12 +│ │ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ │ ├── lock_api v0.4.11 (*) +│ │ │ │ │ │ │ └── parking_lot_core v0.8.6 +│ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ ├── instant v0.1.12 (*) +│ │ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ │ │ ├── regex v1.10.2 (*) +│ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ │ │ ├── sharded-slab v0.1.7 +│ │ │ │ │ │ │ └── lazy_static v1.4.0 +│ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ ├── thread_local v1.1.7 +│ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ └── once_cell v1.18.0 +│ │ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ │ ├── tracing-core v0.1.32 (*) +│ │ │ │ │ │ ├── tracing-log v0.1.4 +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ │ │ └── tracing-core v0.1.32 (*) +│ │ │ │ │ │ └── tracing-serde v0.1.3 +│ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ └── tracing-core v0.1.32 (*) +│ │ │ │ │ ├── sp-wasm-interface v17.0.0 +│ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ │ └── wasmtime v8.0.1 +│ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ ├── bincode v1.3.3 +│ │ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ ├── indexmap v1.9.3 +│ │ │ │ │ │ │ ├── hashbrown v0.12.3 (*) +│ │ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── autocfg v1.1.0 +│ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── object v0.30.4 +│ │ │ │ │ │ │ ├── crc32fast v1.3.2 +│ │ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ │ ├── hashbrown v0.13.2 +│ │ │ │ │ │ │ │ └── ahash v0.8.6 +│ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ ├── getrandom v0.2.10 (*) +│ │ │ │ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ │ │ │ └── zerocopy v0.7.14 +│ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ └── version_check v0.9.4 +│ │ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ │ └── memchr v2.6.4 +│ │ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ │ │ │ ├── psm v0.1.21 +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ │ │ ├── rayon v1.8.0 +│ │ │ │ │ │ │ ├── either v1.9.0 +│ │ │ │ │ │ │ └── rayon-core v1.12.0 +│ │ │ │ │ │ │ ├── crossbeam-deque v0.8.3 +│ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ ├── crossbeam-epoch v0.9.15 +│ │ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ │ ├── crossbeam-utils v0.8.16 +│ │ │ │ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ │ ├── memoffset v0.9.0 +│ │ │ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ │ │ └── autocfg v1.1.0 +│ │ │ │ │ │ │ │ │ └── scopeguard v1.2.0 +│ │ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ │ └── autocfg v1.1.0 +│ │ │ │ │ │ │ │ └── crossbeam-utils v0.8.16 (*) +│ │ │ │ │ │ │ └── crossbeam-utils v0.8.16 (*) +│ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ │ ├── wasmparser v0.102.0 +│ │ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ │ └── url v2.4.1 +│ │ │ │ │ │ │ ├── form_urlencoded v1.2.0 +│ │ │ │ │ │ │ │ └── percent-encoding v2.3.0 +│ │ │ │ │ │ │ ├── idna v0.4.0 +│ │ │ │ │ │ │ │ ├── unicode-bidi v0.3.13 +│ │ │ │ │ │ │ │ └── unicode-normalization v0.1.22 +│ │ │ │ │ │ │ │ └── tinyvec v1.6.0 +│ │ │ │ │ │ │ │ └── tinyvec_macros v0.1.1 +│ │ │ │ │ │ │ └── percent-encoding v2.3.0 +│ │ │ │ │ │ ├── wasmtime-cache v8.0.1 +│ │ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ │ ├── base64 v0.21.5 +│ │ │ │ │ │ │ ├── bincode v1.3.3 (*) +│ │ │ │ │ │ │ ├── directories-next v2.0.0 +│ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ └── dirs-sys-next v0.1.2 +│ │ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ │ ├── file-per-thread-logger v0.1.6 +│ │ │ │ │ │ │ │ ├── env_logger v0.10.0 +│ │ │ │ │ │ │ │ │ ├── humantime v2.1.0 +│ │ │ │ │ │ │ │ │ ├── is-terminal v0.4.9 +│ │ │ │ │ │ │ │ │ │ └── rustix v0.38.20 +│ │ │ │ │ │ │ │ │ │ ├── bitflags v2.4.1 +│ │ │ │ │ │ │ │ │ │ ├── errno v0.3.5 +│ │ │ │ │ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ │ │ ├── regex v1.10.2 (*) +│ │ │ │ │ │ │ │ │ └── termcolor v1.3.0 +│ │ │ │ │ │ │ │ └── log v0.4.20 +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── rustix v0.36.16 +│ │ │ │ │ │ │ │ ├── bitflags v1.3.2 +│ │ │ │ │ │ │ │ ├── errno v0.3.5 (*) +│ │ │ │ │ │ │ │ ├── io-lifetimes v1.0.11 +│ │ │ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ │ │ ├── toml v0.5.11 (*) +│ │ │ │ │ │ │ └── zstd v0.11.2+zstd.1.5.2 +│ │ │ │ │ │ │ └── zstd-safe v5.0.2+zstd.1.5.2 +│ │ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ │ └── zstd-sys v2.0.9+zstd.1.5.5 +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ ├── cc v1.0.83 (*) +│ │ │ │ │ │ │ └── pkg-config v0.3.27 +│ │ │ │ │ │ ├── wasmtime-cranelift v8.0.1 +│ │ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ │ ├── cranelift-codegen v0.95.1 +│ │ │ │ │ │ │ │ ├── bumpalo v3.14.0 +│ │ │ │ │ │ │ │ ├── cranelift-bforest v0.95.1 +│ │ │ │ │ │ │ │ │ └── cranelift-entity v0.95.1 +│ │ │ │ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ │ │ │ ├── cranelift-codegen-shared v0.95.1 +│ │ │ │ │ │ │ │ ├── cranelift-entity v0.95.1 (*) +│ │ │ │ │ │ │ │ ├── gimli v0.27.3 +│ │ │ │ │ │ │ │ │ ├── fallible-iterator v0.2.0 +│ │ │ │ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ │ │ │ └── stable_deref_trait v1.2.0 +│ │ │ │ │ │ │ │ ├── hashbrown v0.13.2 (*) +│ │ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ │ ├── regalloc2 v0.6.1 +│ │ │ │ │ │ │ │ │ ├── fxhash v0.2.1 +│ │ │ │ │ │ │ │ │ │ └── byteorder v1.5.0 +│ │ │ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ │ │ ├── slice-group-by v0.3.1 +│ │ │ │ │ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ │ │ └── target-lexicon v0.12.12 +│ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ ├── cranelift-codegen-meta v0.95.1 +│ │ │ │ │ │ │ │ │ └── cranelift-codegen-shared v0.95.1 +│ │ │ │ │ │ │ │ └── cranelift-isle v0.95.1 +│ │ │ │ │ │ │ ├── cranelift-entity v0.95.1 (*) +│ │ │ │ │ │ │ ├── cranelift-frontend v0.95.1 +│ │ │ │ │ │ │ │ ├── cranelift-codegen v0.95.1 (*) +│ │ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ │ │ └── target-lexicon v0.12.12 +│ │ │ │ │ │ │ ├── cranelift-native v0.95.1 +│ │ │ │ │ │ │ │ ├── cranelift-codegen v0.95.1 (*) +│ │ │ │ │ │ │ │ └── target-lexicon v0.12.12 +│ │ │ │ │ │ │ ├── cranelift-wasm v0.95.1 +│ │ │ │ │ │ │ │ ├── cranelift-codegen v0.95.1 (*) +│ │ │ │ │ │ │ │ ├── cranelift-entity v0.95.1 (*) +│ │ │ │ │ │ │ │ ├── cranelift-frontend v0.95.1 (*) +│ │ │ │ │ │ │ │ ├── itertools v0.10.5 (*) +│ │ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ │ │ ├── wasmparser v0.102.0 (*) +│ │ │ │ │ │ │ │ └── wasmtime-types v8.0.1 +│ │ │ │ │ │ │ │ ├── cranelift-entity v0.95.1 (*) +│ │ │ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ │ │ └── wasmparser v0.102.0 (*) +│ │ │ │ │ │ │ ├── gimli v0.27.3 (*) +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ │ ├── wasmparser v0.102.0 (*) +│ │ │ │ │ │ │ ├── wasmtime-cranelift-shared v8.0.1 +│ │ │ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ │ │ ├── cranelift-codegen v0.95.1 (*) +│ │ │ │ │ │ │ │ ├── cranelift-native v0.95.1 (*) +│ │ │ │ │ │ │ │ ├── gimli v0.27.3 (*) +│ │ │ │ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ │ │ │ └── wasmtime-environ v8.0.1 +│ │ │ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ │ │ ├── cranelift-entity v0.95.1 (*) +│ │ │ │ │ │ │ │ ├── gimli v0.27.3 (*) +│ │ │ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ │ │ ├── wasmparser v0.102.0 (*) +│ │ │ │ │ │ │ │ └── wasmtime-types v8.0.1 (*) +│ │ │ │ │ │ │ └── wasmtime-environ v8.0.1 (*) +│ │ │ │ │ │ ├── wasmtime-environ v8.0.1 (*) +│ │ │ │ │ │ ├── wasmtime-jit v8.0.1 +│ │ │ │ │ │ │ ├── addr2line v0.19.0 +│ │ │ │ │ │ │ │ └── gimli v0.27.3 (*) +│ │ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ │ ├── bincode v1.3.3 (*) +│ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ ├── cpp_demangle v0.3.5 +│ │ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ │ ├── gimli v0.27.3 (*) +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ │ │ ├── rustc-demangle v0.1.23 +│ │ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ │ │ ├── wasmtime-environ v8.0.1 (*) +│ │ │ │ │ │ │ ├── wasmtime-jit-debug v8.0.1 +│ │ │ │ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ │ │ │ └── once_cell v1.18.0 +│ │ │ │ │ │ │ ├── wasmtime-jit-icache-coherence v8.0.1 +│ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ │ └── wasmtime-runtime v8.0.1 +│ │ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── mach v0.3.2 +│ │ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ │ ├── memfd v0.6.4 +│ │ │ │ │ │ │ │ └── rustix v0.38.20 (*) +│ │ │ │ │ │ │ ├── memoffset v0.8.0 +│ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ └── autocfg v1.1.0 +│ │ │ │ │ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ │ ├── rustix v0.36.16 (*) +│ │ │ │ │ │ │ ├── wasmtime-asm-macros v8.0.1 +│ │ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ │ ├── wasmtime-environ v8.0.1 (*) +│ │ │ │ │ │ │ └── wasmtime-jit-debug v8.0.1 (*) +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ │ │ └── wasmtime-runtime v8.0.1 (*) +│ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ ├── sp-storage v16.0.0 (*) +│ │ │ │ ├── ss58-registry v1.43.0 +│ │ │ │ │ └── num-format v0.4.4 +│ │ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ │ └── itoa v1.0.9 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ ├── Inflector v0.11.4 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── serde_json v1.0.107 +│ │ │ │ │ │ ├── itoa v1.0.9 +│ │ │ │ │ │ ├── ryu v1.0.15 +│ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ └── unicode-xid v0.2.4 +│ │ │ │ ├── substrate-bip39 v0.4.4 +│ │ │ │ │ ├── hmac v0.11.0 +│ │ │ │ │ │ ├── crypto-mac v0.11.1 +│ │ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ │ └── digest v0.9.0 (*) +│ │ │ │ │ ├── pbkdf2 v0.8.0 +│ │ │ │ │ │ └── crypto-mac v0.11.1 (*) +│ │ │ │ │ ├── schnorrkel v0.9.1 (*) +│ │ │ │ │ ├── sha2 v0.9.9 (*) +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ ├── tiny-bip39 v1.0.0 +│ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ ├── hmac v0.12.1 (*) +│ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ ├── pbkdf2 v0.11.0 +│ │ │ │ │ │ └── digest v0.10.7 (*) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── rustc-hash v1.1.0 +│ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ ├── unicode-normalization v0.1.22 (*) +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ ├── sp-metadata-ir v0.3.0 +│ │ │ │ ├── frame-metadata v16.0.0 (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ └── sp-std v11.0.0 +│ │ │ ├── sp-runtime v27.0.0 +│ │ │ │ ├── either v1.9.0 +│ │ │ │ ├── hash256-std-hasher v0.15.2 (*) +│ │ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── sp-application-crypto v26.0.0 +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ ├── sp-io v26.0.0 +│ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ ├── ed25519-dalek v2.0.0 +│ │ │ │ │ │ │ ├── curve25519-dalek v4.1.1 +│ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ ├── platforms v3.1.2 +│ │ │ │ │ │ │ │ └── rustc_version v0.4.0 +│ │ │ │ │ │ │ │ └── semver v1.0.20 +│ │ │ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ │ │ ├── ed25519 v2.2.3 +│ │ │ │ │ │ │ │ └── signature v2.1.0 (*) +│ │ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ │ │ ├── libsecp256k1 v0.7.1 (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ │ ├── secp256k1 v0.24.3 (*) +│ │ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ │ │ │ ├── sp-keystore v0.30.0 +│ │ │ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ │ │ ├── sp-runtime-interface v20.0.0 (*) +│ │ │ │ │ │ ├── sp-state-machine v0.31.0 +│ │ │ │ │ │ │ ├── hash-db v0.16.0 +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ │ │ │ │ ├── sp-panic-handler v11.0.0 +│ │ │ │ │ │ │ │ ├── backtrace v0.3.69 +│ │ │ │ │ │ │ │ │ ├── addr2line v0.21.0 +│ │ │ │ │ │ │ │ │ │ └── gimli v0.28.0 +│ │ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ │ │ │ ├── miniz_oxide v0.7.1 +│ │ │ │ │ │ │ │ │ │ └── adler v1.0.2 +│ │ │ │ │ │ │ │ │ ├── object v0.32.1 +│ │ │ │ │ │ │ │ │ │ └── memchr v2.6.4 +│ │ │ │ │ │ │ │ │ └── rustc-demangle v0.1.23 +│ │ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ │ │ │ └── regex v1.10.2 (*) +│ │ │ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ │ │ ├── sp-trie v25.0.0 +│ │ │ │ │ │ │ │ ├── ahash v0.8.6 (*) +│ │ │ │ │ │ │ │ ├── hash-db v0.16.0 +│ │ │ │ │ │ │ │ ├── hashbrown v0.13.2 (*) +│ │ │ │ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ │ │ │ ├── memory-db v0.32.0 +│ │ │ │ │ │ │ │ │ └── hash-db v0.16.0 +│ │ │ │ │ │ │ │ ├── nohash-hasher v0.2.0 +│ │ │ │ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ │ │ │ │ ├── schnellru v0.2.1 +│ │ │ │ │ │ │ │ │ ├── ahash v0.8.6 (*) +│ │ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ │ └── hashbrown v0.13.2 (*) +│ │ │ │ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ │ │ │ ├── trie-db v0.28.0 +│ │ │ │ │ │ │ │ │ ├── hash-db v0.16.0 +│ │ │ │ │ │ │ │ │ ├── hashbrown v0.13.2 (*) +│ │ │ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ │ │ ├── rustc-hex v2.1.0 +│ │ │ │ │ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ │ │ │ │ └── trie-root v0.18.0 +│ │ │ │ │ │ │ │ └── hash-db v0.16.0 +│ │ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ │ │ └── trie-db v0.28.0 (*) +│ │ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ │ ├── sp-tracing v13.0.0 (*) +│ │ │ │ │ │ ├── sp-trie v25.0.0 (*) +│ │ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ │ └── tracing-core v0.1.32 (*) +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── rustversion v1.0.14 (proc-macro) +│ │ │ │ │ └── sp-std v11.0.0 +│ │ │ │ ├── sp-arithmetic v19.0.0 +│ │ │ │ │ ├── integer-sqrt v0.1.5 +│ │ │ │ │ │ └── num-traits v0.2.17 (*) +│ │ │ │ │ ├── num-traits v0.2.17 (*) +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-io v26.0.0 (*) +│ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ └── sp-weights v23.0.0 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ ├── sp-arithmetic v19.0.0 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-debug-derive v11.0.0 (proc-macro) (*) +│ │ │ │ └── sp-std v11.0.0 +│ │ │ ├── sp-state-machine v0.31.0 (*) +│ │ │ ├── sp-std v11.0.0 +│ │ │ ├── sp-trie v25.0.0 (*) +│ │ │ ├── sp-version v25.0.0 +│ │ │ │ ├── impl-serde v0.4.0 (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── parity-wasm v0.45.0 +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── sp-core-hashing-proc-macro v12.0.0 (proc-macro) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ ├── sp-core-hashing v12.0.0 +│ │ │ │ │ │ ├── blake2b_simd v1.0.2 +│ │ │ │ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ │ │ │ └── constant_time_eq v0.3.0 +│ │ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ │ ├── sha3 v0.10.8 +│ │ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ │ └── keccak v0.1.4 (*) +│ │ │ │ │ │ └── twox-hash v1.6.3 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ ├── sp-version-proc-macro v11.0.0 (proc-macro) +│ │ │ │ │ ├── parity-scale-codec v3.6.5 +│ │ │ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ │ │ ├── byte-slice-cast v1.2.2 +│ │ │ │ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ │ │ │ ├── parity-scale-codec-derive v3.6.5 (proc-macro) (*) +│ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ └── thiserror v1.0.50 (*) +│ │ ├── sp-arithmetic v19.0.0 (*) +│ │ ├── sp-core v24.0.0 (*) +│ │ ├── sp-core-hashing-proc-macro v12.0.0 (proc-macro) (*) +│ │ ├── sp-debug-derive v11.0.0 (proc-macro) (*) +│ │ ├── sp-genesis-builder v0.3.0 +│ │ │ ├── serde_json v1.0.107 (*) +│ │ │ ├── sp-api v22.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ └── sp-std v11.0.0 +│ │ ├── sp-inherents v22.0.0 +│ │ │ ├── async-trait v0.1.74 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ ├── sp-std v11.0.0 +│ │ │ └── thiserror v1.0.50 (*) +│ │ ├── sp-io v26.0.0 (*) +│ │ ├── sp-metadata-ir v0.3.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ ├── sp-staking v22.0.0 +│ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ ├── serde v1.0.189 (*) +│ │ │ ├── sp-core v24.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ └── sp-std v11.0.0 +│ │ ├── sp-state-machine v0.31.0 (*) +│ │ ├── sp-std v11.0.0 +│ │ ├── sp-tracing v13.0.0 (*) +│ │ ├── sp-weights v23.0.0 (*) +│ │ ├── static_assertions v1.1.0 +│ │ └── tt-call v1.0.9 +│ ├── frame-support-procedural v19.0.0 (proc-macro) (*) +│ ├── frame-system v24.0.0 +│ │ ├── cfg-if v1.0.0 +│ │ ├── frame-support v24.0.0 (*) +│ │ ├── log v0.4.20 +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── serde v1.0.189 (*) +│ │ ├── sp-core v24.0.0 (*) +│ │ ├── sp-io v26.0.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ ├── sp-std v11.0.0 +│ │ ├── sp-version v25.0.0 (*) +│ │ └── sp-weights v23.0.0 (*) +│ ├── linregress v0.5.3 +│ │ └── nalgebra v0.32.3 +│ │ ├── approx v0.5.1 +│ │ │ └── num-traits v0.2.17 (*) +│ │ ├── matrixmultiply v0.3.8 +│ │ │ └── rawpointer v0.2.1 +│ │ │ [build-dependencies] +│ │ │ └── autocfg v1.1.0 +│ │ ├── nalgebra-macros v0.2.1 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ ├── num-complex v0.4.4 +│ │ │ └── num-traits v0.2.17 (*) +│ │ ├── num-rational v0.4.1 +│ │ │ ├── num-integer v0.1.45 +│ │ │ │ └── num-traits v0.2.17 (*) +│ │ │ │ [build-dependencies] +│ │ │ │ └── autocfg v1.1.0 +│ │ │ └── num-traits v0.2.17 (*) +│ │ │ [build-dependencies] +│ │ │ └── autocfg v1.1.0 +│ │ ├── num-traits v0.2.17 (*) +│ │ ├── simba v0.8.1 +│ │ │ ├── approx v0.5.1 (*) +│ │ │ ├── num-complex v0.4.4 (*) +│ │ │ ├── num-traits v0.2.17 (*) +│ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ └── wide v0.7.13 +│ │ │ ├── bytemuck v1.14.0 +│ │ │ └── safe_arch v0.7.1 +│ │ │ └── bytemuck v1.14.0 +│ │ └── typenum v1.17.0 +│ ├── log v0.4.20 +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── paste v1.0.14 (proc-macro) +│ ├── scale-info v2.10.0 (*) +│ ├── serde v1.0.189 (*) +│ ├── sp-api v22.0.0 (*) +│ ├── sp-application-crypto v26.0.0 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-io v26.0.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ ├── sp-runtime-interface v20.0.0 (*) +│ ├── sp-std v11.0.0 +│ ├── sp-storage v16.0.0 (*) +│ └── static_assertions v1.1.0 +├── frame-benchmarking-cli v28.0.0 +│ ├── Inflector v0.11.4 (*) +│ ├── array-bytes v6.1.0 +│ ├── chrono v0.4.31 (*) +│ ├── clap v4.4.7 (*) +│ ├── comfy-table v7.1.0 +│ │ ├── strum v0.25.0 +│ │ ├── strum_macros v0.25.3 (proc-macro) +│ │ │ ├── heck v0.4.1 +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ ├── rustversion v1.0.14 (proc-macro) +│ │ │ └── syn v2.0.38 (*) +│ │ └── unicode-width v0.1.11 +│ ├── frame-benchmarking v24.0.0 (*) +│ ├── frame-support v24.0.0 (*) +│ ├── frame-system v24.0.0 (*) +│ ├── gethostname v0.2.3 +│ │ └── libc v0.2.149 +│ ├── handlebars v4.4.0 +│ │ ├── log v0.4.20 +│ │ ├── pest v2.7.5 +│ │ │ ├── memchr v2.6.4 +│ │ │ ├── thiserror v1.0.50 (*) +│ │ │ └── ucd-trie v0.1.6 +│ │ ├── pest_derive v2.7.5 (proc-macro) +│ │ │ ├── pest v2.7.5 (*) +│ │ │ └── pest_generator v2.7.5 +│ │ │ ├── pest v2.7.5 (*) +│ │ │ ├── pest_meta v2.7.5 +│ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ └── pest v2.7.5 (*) +│ │ │ │ [build-dependencies] +│ │ │ │ └── sha2 v0.10.8 (*) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v2.0.38 (*) +│ │ ├── serde v1.0.189 (*) +│ │ ├── serde_json v1.0.107 (*) +│ │ └── thiserror v1.0.50 (*) +│ ├── itertools v0.10.5 (*) +│ ├── lazy_static v1.4.0 +│ ├── linked-hash-map v0.5.6 +│ ├── log v0.4.20 +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── rand v0.8.5 (*) +│ ├── rand_pcg v0.3.1 +│ │ └── rand_core v0.6.4 (*) +│ ├── sc-block-builder v0.29.0 +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── sc-client-api v24.0.0 +│ │ │ ├── fnv v1.0.7 +│ │ │ ├── futures v0.3.28 (*) +│ │ │ ├── log v0.4.20 +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ ├── sc-executor v0.28.0 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ ├── sc-executor-common v0.25.0 +│ │ │ │ │ ├── sc-allocator v19.0.0 +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ │ ├── sp-wasm-interface v17.0.0 (*) +│ │ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ │ ├── sp-maybe-compressed-blob v8.0.0 +│ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ └── zstd v0.12.4 +│ │ │ │ │ │ └── zstd-safe v6.0.6 +│ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ └── zstd-sys v2.0.9+zstd.1.5.5 (*) +│ │ │ │ │ ├── sp-wasm-interface v17.0.0 (*) +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ └── wasm-instrument v0.3.0 +│ │ │ │ │ └── parity-wasm v0.45.0 +│ │ │ │ ├── sc-executor-wasmtime v0.25.0 +│ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── rustix v0.36.16 (*) +│ │ │ │ │ ├── sc-allocator v19.0.0 (*) +│ │ │ │ │ ├── sc-executor-common v0.25.0 (*) +│ │ │ │ │ ├── sp-runtime-interface v20.0.0 (*) +│ │ │ │ │ ├── sp-wasm-interface v17.0.0 (*) +│ │ │ │ │ └── wasmtime v8.0.1 (*) +│ │ │ │ ├── schnellru v0.2.1 (*) +│ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ │ ├── sp-io v26.0.0 (*) +│ │ │ │ ├── sp-panic-handler v11.0.0 (*) +│ │ │ │ ├── sp-runtime-interface v20.0.0 (*) +│ │ │ │ ├── sp-trie v25.0.0 (*) +│ │ │ │ ├── sp-version v25.0.0 (*) +│ │ │ │ ├── sp-wasm-interface v17.0.0 (*) +│ │ │ │ └── tracing v0.1.40 (*) +│ │ │ ├── sc-transaction-pool-api v24.0.0 +│ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── sp-blockchain v24.0.0 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ ├── schnellru v0.2.1 (*) +│ │ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ │ ├── sp-consensus v0.28.0 +│ │ │ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ │ ├── sp-inherents v22.0.0 (*) +│ │ │ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ │ │ ├── sp-state-machine v0.31.0 (*) +│ │ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ │ ├── sp-database v8.0.0 +│ │ │ │ │ │ ├── kvdb v0.13.0 +│ │ │ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ │ │ └── parking_lot v0.12.1 (*) +│ │ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ │ ├── sp-state-machine v0.31.0 (*) +│ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ ├── sc-utils v10.0.0 +│ │ │ │ ├── async-channel v1.9.0 +│ │ │ │ │ ├── concurrent-queue v2.3.0 +│ │ │ │ │ │ └── crossbeam-utils v0.8.16 (*) +│ │ │ │ │ ├── event-listener v2.5.3 +│ │ │ │ │ └── futures-core v0.3.28 +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ ├── prometheus v0.13.3 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ ├── memchr v2.6.4 +│ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ └── sp-arithmetic v19.0.0 (*) +│ │ │ ├── sp-api v22.0.0 (*) +│ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ ├── sp-consensus v0.28.0 (*) +│ │ │ ├── sp-core v24.0.0 (*) +│ │ │ ├── sp-database v8.0.0 (*) +│ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ ├── sp-state-machine v0.31.0 (*) +│ │ │ ├── sp-statement-store v6.0.0 +│ │ │ │ ├── aes-gcm v0.10.3 +│ │ │ │ │ ├── aead v0.5.2 +│ │ │ │ │ │ ├── crypto-common v0.1.6 (*) +│ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ ├── aes v0.8.3 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ ├── cipher v0.4.4 +│ │ │ │ │ │ │ ├── crypto-common v0.1.6 (*) +│ │ │ │ │ │ │ └── inout v0.1.3 +│ │ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ │ └── cpufeatures v0.2.10 (*) +│ │ │ │ │ ├── cipher v0.4.4 (*) +│ │ │ │ │ ├── ctr v0.9.2 +│ │ │ │ │ │ └── cipher v0.4.4 (*) +│ │ │ │ │ ├── ghash v0.5.0 +│ │ │ │ │ │ ├── opaque-debug v0.3.0 +│ │ │ │ │ │ └── polyval v0.6.1 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ ├── cpufeatures v0.2.10 (*) +│ │ │ │ │ │ ├── opaque-debug v0.3.0 +│ │ │ │ │ │ └── universal-hash v0.5.1 +│ │ │ │ │ │ ├── crypto-common v0.1.6 (*) +│ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ ├── curve25519-dalek v4.1.1 (*) +│ │ │ │ ├── ed25519-dalek v2.0.0 (*) +│ │ │ │ ├── hkdf v0.12.3 +│ │ │ │ │ └── hmac v0.12.1 (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ ├── sp-application-crypto v26.0.0 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ ├── sp-runtime-interface v20.0.0 (*) +│ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ └── x25519-dalek v2.0.0 +│ │ │ │ ├── curve25519-dalek v4.1.1 (*) +│ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ ├── sp-storage v16.0.0 (*) +│ │ │ ├── sp-trie v25.0.0 (*) +│ │ │ └── substrate-prometheus-endpoint v0.15.0 +│ │ │ ├── hyper v0.14.27 +│ │ │ │ ├── bytes v1.5.0 +│ │ │ │ ├── futures-channel v0.3.28 (*) +│ │ │ │ ├── futures-core v0.3.28 +│ │ │ │ ├── futures-util v0.3.28 (*) +│ │ │ │ ├── h2 v0.3.21 +│ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ ├── futures-core v0.3.28 +│ │ │ │ │ ├── futures-sink v0.3.28 +│ │ │ │ │ ├── futures-util v0.3.28 (*) +│ │ │ │ │ ├── http v0.2.9 +│ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ │ └── itoa v1.0.9 +│ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ ├── slab v0.4.9 (*) +│ │ │ │ │ ├── tokio v1.33.0 +│ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ ├── mio v0.8.9 +│ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ ├── num_cpus v1.16.0 (*) +│ │ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ │ ├── signal-hook-registry v1.4.1 +│ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ ├── socket2 v0.5.5 +│ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ └── tokio-macros v2.1.0 (proc-macro) +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ ├── tokio-util v0.7.10 +│ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ ├── futures-core v0.3.28 +│ │ │ │ │ │ ├── futures-io v0.3.28 +│ │ │ │ │ │ ├── futures-sink v0.3.28 +│ │ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ │ └── tracing v0.1.40 (*) +│ │ │ │ │ └── tracing v0.1.40 (*) +│ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ ├── http-body v0.4.5 +│ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ │ └── pin-project-lite v0.2.13 +│ │ │ │ ├── httparse v1.8.0 +│ │ │ │ ├── httpdate v1.0.3 +│ │ │ │ ├── itoa v1.0.9 +│ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ ├── socket2 v0.4.10 +│ │ │ │ │ └── libc v0.2.149 +│ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ ├── tower-service v0.3.2 +│ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ └── want v0.3.1 +│ │ │ │ └── try-lock v0.2.4 +│ │ │ ├── log v0.4.20 +│ │ │ ├── prometheus v0.13.3 (*) +│ │ │ ├── thiserror v1.0.50 (*) +│ │ │ └── tokio v1.33.0 (*) +│ │ ├── sp-api v22.0.0 (*) +│ │ ├── sp-block-builder v22.0.0 +│ │ │ ├── sp-api v22.0.0 (*) +│ │ │ ├── sp-inherents v22.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ └── sp-std v11.0.0 +│ │ ├── sp-blockchain v24.0.0 (*) +│ │ ├── sp-core v24.0.0 (*) +│ │ ├── sp-inherents v22.0.0 (*) +│ │ └── sp-runtime v27.0.0 (*) +│ ├── sc-cli v0.32.0 +│ │ ├── array-bytes v6.1.0 +│ │ ├── chrono v0.4.31 (*) +│ │ ├── clap v4.4.7 (*) +│ │ ├── fdlimit v0.2.1 +│ │ │ └── libc v0.2.149 +│ │ ├── futures v0.3.28 (*) +│ │ ├── libp2p-identity v0.1.3 +│ │ │ ├── bs58 v0.4.0 +│ │ │ ├── ed25519-dalek v2.0.0 (*) +│ │ │ ├── log v0.4.20 +│ │ │ ├── multiaddr v0.17.1 +│ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ ├── data-encoding v2.4.0 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── multibase v0.9.1 +│ │ │ │ │ ├── base-x v0.2.11 +│ │ │ │ │ ├── data-encoding v2.4.0 +│ │ │ │ │ └── data-encoding-macro v0.1.13 +│ │ │ │ │ ├── data-encoding v2.4.0 +│ │ │ │ │ └── data-encoding-macro-internal v0.1.11 (proc-macro) +│ │ │ │ │ ├── data-encoding v2.4.0 +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ ├── multihash v0.17.0 +│ │ │ │ │ ├── blake2b_simd v1.0.2 (*) +│ │ │ │ │ ├── blake2s_simd v1.0.2 +│ │ │ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ │ │ └── constant_time_eq v0.3.0 +│ │ │ │ │ ├── blake3 v1.5.0 +│ │ │ │ │ │ ├── arrayref v0.3.7 +│ │ │ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ └── constant_time_eq v0.3.0 +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ │ ├── core2 v0.4.0 +│ │ │ │ │ │ └── memchr v2.6.4 +│ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ ├── multihash-derive v0.8.1 (proc-macro) +│ │ │ │ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ │ │ │ ├── proc-macro-error v1.0.4 (*) +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ ├── syn v1.0.109 (*) +│ │ │ │ │ │ └── synstructure v0.12.6 +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ ├── syn v1.0.109 (*) +│ │ │ │ │ │ └── unicode-xid v0.2.4 +│ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ ├── sha3 v0.10.8 (*) +│ │ │ │ │ └── unsigned-varint v0.7.2 +│ │ │ │ │ ├── asynchronous-codec v0.6.2 +│ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ ├── futures-sink v0.3.28 +│ │ │ │ │ │ ├── futures-util v0.3.28 (*) +│ │ │ │ │ │ ├── memchr v2.6.4 +│ │ │ │ │ │ └── pin-project-lite v0.2.13 +│ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ ├── futures-io v0.3.28 +│ │ │ │ │ └── futures-util v0.3.28 (*) +│ │ │ │ ├── percent-encoding v2.3.0 +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── static_assertions v1.1.0 +│ │ │ │ ├── unsigned-varint v0.7.2 (*) +│ │ │ │ └── url v2.4.1 (*) +│ │ │ ├── multihash v0.17.0 (*) +│ │ │ ├── quick-protobuf v0.8.1 +│ │ │ │ └── byteorder v1.5.0 +│ │ │ ├── rand v0.8.5 (*) +│ │ │ ├── sha2 v0.10.8 (*) +│ │ │ ├── thiserror v1.0.50 (*) +│ │ │ └── zeroize v1.6.0 (*) +│ │ ├── log v0.4.20 +│ │ ├── names v0.13.0 +│ │ │ └── rand v0.8.5 (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── rand v0.8.5 (*) +│ │ ├── regex v1.10.2 (*) +│ │ ├── rpassword v7.2.0 +│ │ │ ├── libc v0.2.149 +│ │ │ └── rtoolbox v0.0.1 +│ │ │ └── libc v0.2.149 +│ │ ├── sc-client-api v24.0.0 (*) +│ │ ├── sc-client-db v0.31.0 +│ │ │ ├── hash-db v0.16.0 +│ │ │ ├── kvdb v0.13.0 (*) +│ │ │ ├── kvdb-memorydb v0.13.0 +│ │ │ │ ├── kvdb v0.13.0 (*) +│ │ │ │ └── parking_lot v0.12.1 (*) +│ │ │ ├── kvdb-rocksdb v0.19.0 +│ │ │ │ ├── kvdb v0.13.0 (*) +│ │ │ │ ├── num_cpus v1.16.0 (*) +│ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ ├── regex v1.10.2 (*) +│ │ │ │ ├── rocksdb v0.21.0 +│ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ └── librocksdb-sys v0.11.0+8.1.1 +│ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ └── tikv-jemalloc-sys v0.5.4+5.3.0-patched +│ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ ├── bindgen v0.65.1 +│ │ │ │ │ │ ├── bitflags v1.3.2 +│ │ │ │ │ │ ├── cexpr v0.6.0 +│ │ │ │ │ │ │ └── nom v7.1.3 +│ │ │ │ │ │ │ ├── memchr v2.6.4 +│ │ │ │ │ │ │ └── minimal-lexical v0.2.1 +│ │ │ │ │ │ ├── clang-sys v1.6.1 +│ │ │ │ │ │ │ ├── glob v0.3.1 +│ │ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ │ └── libloading v0.7.4 +│ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── glob v0.3.1 +│ │ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ │ ├── lazycell v1.3.0 +│ │ │ │ │ │ ├── peeking_take_while v0.1.2 +│ │ │ │ │ │ ├── prettyplease v0.2.15 +│ │ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ ├── regex v1.10.2 (*) +│ │ │ │ │ │ ├── rustc-hash v1.1.0 +│ │ │ │ │ │ ├── shlex v1.2.0 +│ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ ├── cc v1.0.83 (*) +│ │ │ │ │ └── glob v0.3.1 +│ │ │ │ └── smallvec v1.11.1 +│ │ │ ├── linked-hash-map v0.5.6 +│ │ │ ├── log v0.4.20 +│ │ │ ├── parity-db v0.4.12 +│ │ │ │ ├── blake2 v0.10.6 (*) +│ │ │ │ ├── crc32fast v1.3.2 (*) +│ │ │ │ ├── fs2 v0.4.3 +│ │ │ │ │ └── libc v0.2.149 +│ │ │ │ ├── hex v0.4.3 +│ │ │ │ ├── libc v0.2.149 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── lz4 v1.24.0 +│ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ └── lz4-sys v1.9.4 +│ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ ├── memmap2 v0.5.10 +│ │ │ │ │ └── libc v0.2.149 +│ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ ├── siphasher v0.3.11 +│ │ │ │ └── snap v1.1.0 +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ ├── sc-state-db v0.26.0 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ └── sp-core v24.0.0 (*) +│ │ │ ├── schnellru v0.2.1 (*) +│ │ │ ├── sp-arithmetic v19.0.0 (*) +│ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ ├── sp-core v24.0.0 (*) +│ │ │ ├── sp-database v8.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ ├── sp-state-machine v0.31.0 (*) +│ │ │ └── sp-trie v25.0.0 (*) +│ │ ├── sc-keystore v21.0.0 +│ │ │ ├── array-bytes v6.1.0 +│ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ ├── serde_json v1.0.107 (*) +│ │ │ ├── sp-application-crypto v26.0.0 (*) +│ │ │ ├── sp-core v24.0.0 (*) +│ │ │ ├── sp-keystore v0.30.0 (*) +│ │ │ └── thiserror v1.0.50 (*) +│ │ ├── sc-network v0.30.0 +│ │ │ ├── array-bytes v6.1.0 +│ │ │ ├── async-channel v1.9.0 (*) +│ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ ├── asynchronous-codec v0.6.2 (*) +│ │ │ ├── bytes v1.5.0 +│ │ │ ├── either v1.9.0 +│ │ │ ├── fnv v1.0.7 +│ │ │ ├── futures v0.3.28 (*) +│ │ │ ├── futures-timer v3.0.2 +│ │ │ ├── ip_network v0.4.1 +│ │ │ ├── libp2p v0.51.3 +│ │ │ │ ├── bytes v1.5.0 +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ ├── getrandom v0.2.10 (*) +│ │ │ │ ├── instant v0.1.12 (*) +│ │ │ │ ├── libp2p-allow-block-list v0.1.1 +│ │ │ │ │ ├── libp2p-core v0.39.2 +│ │ │ │ │ │ ├── either v1.9.0 +│ │ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ │ │ ├── instant v0.1.12 (*) +│ │ │ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── multiaddr v0.17.1 (*) +│ │ │ │ │ │ ├── multihash v0.17.0 (*) +│ │ │ │ │ │ ├── multistream-select v0.12.1 +│ │ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── pin-project v1.1.3 +│ │ │ │ │ │ │ │ └── pin-project-internal v1.1.3 (proc-macro) +│ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ │ └── unsigned-varint v0.7.2 (*) +│ │ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ │ ├── pin-project v1.1.3 (*) +│ │ │ │ │ │ ├── quick-protobuf v0.8.1 (*) +│ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ ├── rw-stream-sink v0.3.0 +│ │ │ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ │ │ ├── pin-project v1.1.3 (*) +│ │ │ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ ├── unsigned-varint v0.7.2 (*) +│ │ │ │ │ │ └── void v1.0.2 +│ │ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ │ ├── libp2p-swarm v0.42.2 +│ │ │ │ │ │ ├── either v1.9.0 +│ │ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ │ │ ├── instant v0.1.12 (*) +│ │ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ │ │ ├── libp2p-swarm-derive v0.32.0 (proc-macro) +│ │ │ │ │ │ │ ├── heck v0.4.1 +│ │ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ │ └── void v1.0.2 +│ │ │ │ │ └── void v1.0.2 +│ │ │ │ ├── libp2p-connection-limits v0.1.0 +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ │ ├── libp2p-swarm v0.42.2 (*) +│ │ │ │ │ └── void v1.0.2 +│ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ ├── libp2p-dns v0.39.0 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ └── trust-dns-resolver v0.22.0 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── futures-util v0.3.28 (*) +│ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ ├── lru-cache v0.1.2 +│ │ │ │ │ │ └── linked-hash-map v0.5.6 +│ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ ├── resolv-conf v0.7.0 +│ │ │ │ │ │ ├── hostname v0.3.1 +│ │ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ │ └── match_cfg v0.1.0 +│ │ │ │ │ │ └── quick-error v1.2.3 +│ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ └── trust-dns-proto v0.22.0 +│ │ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── data-encoding v2.4.0 +│ │ │ │ │ ├── enum-as-inner v0.5.1 (proc-macro) +│ │ │ │ │ │ ├── heck v0.4.1 +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ │ ├── futures-channel v0.3.28 (*) +│ │ │ │ │ ├── futures-io v0.3.28 +│ │ │ │ │ ├── futures-util v0.3.28 (*) +│ │ │ │ │ ├── idna v0.2.3 +│ │ │ │ │ │ ├── matches v0.1.10 +│ │ │ │ │ │ ├── unicode-bidi v0.3.13 +│ │ │ │ │ │ └── unicode-normalization v0.1.22 (*) +│ │ │ │ │ ├── ipnet v2.9.0 +│ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ ├── socket2 v0.4.10 (*) +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ ├── tinyvec v1.6.0 (*) +│ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ └── url v2.4.1 (*) +│ │ │ │ ├── libp2p-identify v0.42.2 +│ │ │ │ │ ├── asynchronous-codec v0.6.2 (*) +│ │ │ │ │ ├── either v1.9.0 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ │ ├── libp2p-swarm v0.42.2 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── lru v0.10.1 +│ │ │ │ │ │ └── hashbrown v0.13.2 (*) +│ │ │ │ │ ├── quick-protobuf v0.8.1 (*) +│ │ │ │ │ ├── quick-protobuf-codec v0.1.0 +│ │ │ │ │ │ ├── asynchronous-codec v0.6.2 (*) +│ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ ├── quick-protobuf v0.8.1 (*) +│ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ └── unsigned-varint v0.7.2 (*) +│ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ └── void v1.0.2 +│ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ ├── libp2p-kad v0.43.3 +│ │ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ │ ├── asynchronous-codec v0.6.2 (*) +│ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ ├── either v1.9.0 +│ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ │ ├── instant v0.1.12 (*) +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ │ ├── libp2p-swarm v0.42.2 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── quick-protobuf v0.8.1 (*) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ ├── uint v0.9.5 (*) +│ │ │ │ │ ├── unsigned-varint v0.7.2 (*) +│ │ │ │ │ └── void v1.0.2 +│ │ │ │ ├── libp2p-mdns v0.43.1 +│ │ │ │ │ ├── data-encoding v2.4.0 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── if-watch v3.1.0 +│ │ │ │ │ │ ├── core-foundation v0.9.3 +│ │ │ │ │ │ │ ├── core-foundation-sys v0.8.4 +│ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ │ ├── if-addrs v0.7.0 +│ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ ├── ipnet v2.9.0 +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── system-configuration v0.5.1 +│ │ │ │ │ │ │ ├── bitflags v1.3.2 +│ │ │ │ │ │ │ ├── core-foundation v0.9.3 (*) +│ │ │ │ │ │ │ └── system-configuration-sys v0.5.0 +│ │ │ │ │ │ │ ├── core-foundation-sys v0.8.4 +│ │ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ │ └── tokio v1.33.0 (*) +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ │ ├── libp2p-swarm v0.42.2 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ ├── socket2 v0.4.10 (*) +│ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ ├── trust-dns-proto v0.22.0 (*) +│ │ │ │ │ └── void v1.0.2 +│ │ │ │ ├── libp2p-noise v0.42.2 +│ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ ├── curve25519-dalek v3.2.0 (*) +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ ├── quick-protobuf v0.8.1 (*) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ ├── snow v0.9.3 +│ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ ├── ring v0.16.20 +│ │ │ │ │ │ │ └── untrusted v0.7.1 +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ └── rustc_version v0.4.0 (*) +│ │ │ │ │ ├── static_assertions v1.1.0 +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ ├── x25519-dalek v1.1.1 +│ │ │ │ │ │ ├── curve25519-dalek v3.2.0 (*) +│ │ │ │ │ │ ├── rand_core v0.5.1 (*) +│ │ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── libp2p-ping v0.42.0 +│ │ │ │ │ ├── either v1.9.0 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ │ ├── instant v0.1.12 (*) +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── libp2p-swarm v0.42.2 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ └── void v1.0.2 +│ │ │ │ ├── libp2p-request-response v0.24.1 +│ │ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── instant v0.1.12 (*) +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ │ ├── libp2p-swarm v0.42.2 (*) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ ├── libp2p-swarm v0.42.2 (*) +│ │ │ │ ├── libp2p-tcp v0.39.0 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ │ ├── if-watch v3.1.0 (*) +│ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── socket2 v0.4.10 (*) +│ │ │ │ │ └── tokio v1.33.0 (*) +│ │ │ │ ├── libp2p-wasm-ext v0.39.0 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── js-sys v0.3.64 +│ │ │ │ │ │ └── wasm-bindgen v0.2.87 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ └── wasm-bindgen-macro v0.2.87 (proc-macro) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ └── wasm-bindgen-macro-support v0.2.87 +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ ├── syn v2.0.38 (*) +│ │ │ │ │ │ ├── wasm-bindgen-backend v0.2.87 +│ │ │ │ │ │ │ ├── bumpalo v3.14.0 +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ │ ├── syn v2.0.38 (*) +│ │ │ │ │ │ │ └── wasm-bindgen-shared v0.2.87 +│ │ │ │ │ │ └── wasm-bindgen-shared v0.2.87 +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── parity-send-wrapper v0.1.0 +│ │ │ │ │ ├── wasm-bindgen v0.2.87 (*) +│ │ │ │ │ └── wasm-bindgen-futures v0.4.37 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── js-sys v0.3.64 (*) +│ │ │ │ │ └── wasm-bindgen v0.2.87 (*) +│ │ │ │ ├── libp2p-websocket v0.41.0 +│ │ │ │ │ ├── either v1.9.0 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── futures-rustls v0.22.2 +│ │ │ │ │ │ ├── futures-io v0.3.28 +│ │ │ │ │ │ ├── rustls v0.20.9 +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── ring v0.16.20 (*) +│ │ │ │ │ │ │ ├── sct v0.7.1 +│ │ │ │ │ │ │ │ ├── ring v0.17.5 +│ │ │ │ │ │ │ │ │ ├── getrandom v0.2.10 (*) +│ │ │ │ │ │ │ │ │ └── untrusted v0.9.0 +│ │ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ │ │ │ │ └── untrusted v0.9.0 +│ │ │ │ │ │ │ └── webpki v0.22.4 +│ │ │ │ │ │ │ ├── ring v0.17.5 (*) +│ │ │ │ │ │ │ └── untrusted v0.9.0 +│ │ │ │ │ │ └── webpki v0.22.4 (*) +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ ├── quicksink v0.1.2 +│ │ │ │ │ │ ├── futures-core v0.3.28 +│ │ │ │ │ │ ├── futures-sink v0.3.28 +│ │ │ │ │ │ └── pin-project-lite v0.1.12 +│ │ │ │ │ ├── rw-stream-sink v0.3.0 (*) +│ │ │ │ │ ├── soketto v0.7.1 +│ │ │ │ │ │ ├── base64 v0.13.1 +│ │ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ │ ├── flate2 v1.0.28 +│ │ │ │ │ │ │ ├── crc32fast v1.3.2 (*) +│ │ │ │ │ │ │ ├── libz-sys v1.1.12 +│ │ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ │ ├── cc v1.0.83 (*) +│ │ │ │ │ │ │ │ ├── pkg-config v0.3.27 +│ │ │ │ │ │ │ │ └── vcpkg v0.2.15 +│ │ │ │ │ │ │ └── miniz_oxide v0.7.1 (*) +│ │ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ │ │ ├── httparse v1.8.0 +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ └── sha-1 v0.9.8 +│ │ │ │ │ │ ├── block-buffer v0.9.0 (*) +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ ├── cpufeatures v0.2.10 (*) +│ │ │ │ │ │ ├── digest v0.9.0 (*) +│ │ │ │ │ │ └── opaque-debug v0.3.0 +│ │ │ │ │ ├── url v2.4.1 (*) +│ │ │ │ │ └── webpki-roots v0.22.6 +│ │ │ │ │ └── webpki v0.22.4 (*) +│ │ │ │ ├── libp2p-yamux v0.43.1 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── libp2p-core v0.39.2 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ └── yamux v0.10.2 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── nohash-hasher v0.2.0 +│ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ ├── multiaddr v0.17.1 (*) +│ │ │ │ └── pin-project v1.1.3 (*) +│ │ │ ├── linked_hash_set v0.1.4 +│ │ │ │ └── linked-hash-map v0.5.6 +│ │ │ ├── log v0.4.20 +│ │ │ ├── mockall v0.11.4 +│ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ ├── downcast v0.11.0 +│ │ │ │ ├── fragile v2.0.0 +│ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ ├── mockall_derive v0.11.4 (proc-macro) +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ ├── predicates v2.1.5 +│ │ │ │ │ ├── difflib v0.4.0 +│ │ │ │ │ ├── float-cmp v0.9.0 +│ │ │ │ │ │ └── num-traits v0.2.17 (*) +│ │ │ │ │ ├── itertools v0.10.5 (*) +│ │ │ │ │ ├── normalize-line-endings v0.3.0 +│ │ │ │ │ ├── predicates-core v1.0.6 +│ │ │ │ │ └── regex v1.10.2 (*) +│ │ │ │ └── predicates-tree v1.0.9 +│ │ │ │ ├── predicates-core v1.0.6 +│ │ │ │ └── termtree v0.4.1 +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ ├── partial_sort v0.2.0 +│ │ │ ├── pin-project v1.1.3 (*) +│ │ │ ├── rand v0.8.5 (*) +│ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ ├── sc-network-common v0.29.0 +│ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ ├── bitflags v1.3.2 +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── sc-consensus v0.29.0 +│ │ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── mockall v0.11.4 (*) +│ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ │ │ ├── sc-utils v10.0.0 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ │ │ ├── sp-consensus v0.28.0 (*) +│ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ │ ├── sp-state-machine v0.31.0 (*) +│ │ │ │ │ ├── substrate-prometheus-endpoint v0.15.0 (*) +│ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ ├── sp-consensus v0.28.0 (*) +│ │ │ │ ├── sp-consensus-grandpa v9.0.0 +│ │ │ │ │ ├── finality-grandpa v0.16.2 +│ │ │ │ │ │ ├── either v1.9.0 +│ │ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── num-traits v0.2.17 (*) +│ │ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ │ └── scale-info v2.10.0 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ │ ├── sp-application-crypto v26.0.0 (*) +│ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ ├── sp-keystore v0.30.0 (*) +│ │ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ │ └── sp-std v11.0.0 +│ │ │ │ └── sp-runtime v27.0.0 (*) +│ │ │ │ [build-dependencies] +│ │ │ │ └── prost-build v0.11.9 +│ │ │ │ ├── bytes v1.5.0 +│ │ │ │ ├── heck v0.4.1 +│ │ │ │ ├── itertools v0.10.5 (*) +│ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── multimap v0.8.3 +│ │ │ │ ├── petgraph v0.6.4 +│ │ │ │ │ ├── fixedbitset v0.4.2 +│ │ │ │ │ └── indexmap v2.0.2 (*) +│ │ │ │ ├── prettyplease v0.1.25 +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ ├── prost v0.11.9 +│ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ └── prost-derive v0.11.9 (proc-macro) +│ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ ├── itertools v0.10.5 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ ├── prost-types v0.11.9 +│ │ │ │ │ └── prost v0.11.9 (*) +│ │ │ │ ├── regex v1.10.2 (*) +│ │ │ │ ├── syn v1.0.109 (*) +│ │ │ │ ├── tempfile v3.8.0 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── fastrand v2.0.1 +│ │ │ │ │ └── rustix v0.38.20 +│ │ │ │ │ ├── bitflags v2.4.1 +│ │ │ │ │ ├── errno v0.3.5 (*) +│ │ │ │ │ └── libc v0.2.149 +│ │ │ │ └── which v4.4.2 +│ │ │ │ ├── either v1.9.0 +│ │ │ │ ├── home v0.5.5 +│ │ │ │ └── rustix v0.38.20 (*) +│ │ │ ├── sc-utils v10.0.0 (*) +│ │ │ ├── serde v1.0.189 (*) +│ │ │ ├── serde_json v1.0.107 (*) +│ │ │ ├── smallvec v1.11.1 +│ │ │ ├── sp-arithmetic v19.0.0 (*) +│ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ ├── sp-core v24.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ ├── substrate-prometheus-endpoint v0.15.0 (*) +│ │ │ ├── thiserror v1.0.50 (*) +│ │ │ ├── unsigned-varint v0.7.2 (*) +│ │ │ ├── wasm-timer v0.2.5 +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── parking_lot v0.11.2 (*) +│ │ │ │ └── pin-utils v0.1.0 +│ │ │ └── zeroize v1.6.0 (*) +│ │ ├── sc-service v0.31.0 +│ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ ├── directories v5.0.1 +│ │ │ │ └── dirs-sys v0.4.1 +│ │ │ │ ├── libc v0.2.149 +│ │ │ │ └── option-ext v0.2.0 +│ │ │ ├── exit-future v0.2.0 +│ │ │ │ └── futures v0.3.28 (*) +│ │ │ ├── futures v0.3.28 (*) +│ │ │ ├── futures-timer v3.0.2 +│ │ │ ├── jsonrpsee v0.16.3 +│ │ │ │ ├── jsonrpsee-core v0.16.3 +│ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ ├── arrayvec v0.7.4 +│ │ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ │ ├── beef v0.5.2 +│ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ ├── futures-channel v0.3.28 (*) +│ │ │ │ │ ├── futures-util v0.3.28 (*) +│ │ │ │ │ ├── globset v0.4.13 +│ │ │ │ │ │ ├── aho-corasick v1.1.2 (*) +│ │ │ │ │ │ ├── bstr v1.7.0 +│ │ │ │ │ │ │ └── memchr v2.6.4 +│ │ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ └── regex v1.10.2 (*) +│ │ │ │ │ ├── hyper v0.14.27 (*) +│ │ │ │ │ ├── jsonrpsee-types v0.16.3 +│ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ ├── beef v0.5.2 (*) +│ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ └── tracing v0.1.40 (*) +│ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── rustc-hash v1.1.0 +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ │ ├── soketto v0.7.1 (*) +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ └── tracing v0.1.40 (*) +│ │ │ │ ├── jsonrpsee-proc-macros v0.16.3 (proc-macro) +│ │ │ │ │ ├── heck v0.4.1 +│ │ │ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ ├── jsonrpsee-server v0.16.3 +│ │ │ │ │ ├── futures-channel v0.3.28 (*) +│ │ │ │ │ ├── futures-util v0.3.28 (*) +│ │ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ │ ├── hyper v0.14.27 (*) +│ │ │ │ │ ├── jsonrpsee-core v0.16.3 (*) +│ │ │ │ │ ├── jsonrpsee-types v0.16.3 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ │ ├── soketto v0.7.1 (*) +│ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ ├── tokio-stream v0.1.14 +│ │ │ │ │ │ ├── futures-core v0.3.28 +│ │ │ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ │ │ └── tokio-util v0.7.10 (*) +│ │ │ │ │ ├── tokio-util v0.7.10 (*) +│ │ │ │ │ ├── tower v0.4.13 +│ │ │ │ │ │ ├── tower-layer v0.3.2 +│ │ │ │ │ │ ├── tower-service v0.3.2 +│ │ │ │ │ │ └── tracing v0.1.40 (*) +│ │ │ │ │ └── tracing v0.1.40 (*) +│ │ │ │ ├── jsonrpsee-types v0.16.3 (*) +│ │ │ │ └── tracing v0.1.40 (*) +│ │ │ ├── log v0.4.20 +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ ├── pin-project v1.1.3 (*) +│ │ │ ├── rand v0.8.5 (*) +│ │ │ ├── sc-block-builder v0.29.0 (*) +│ │ │ ├── sc-chain-spec v23.0.0 +│ │ │ │ ├── memmap2 v0.5.10 (*) +│ │ │ │ ├── sc-chain-spec-derive v9.0.0 (proc-macro) +│ │ │ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ │ ├── sc-executor v0.28.0 (*) +│ │ │ │ ├── sc-network v0.30.0 (*) +│ │ │ │ ├── sc-telemetry v11.0.0 +│ │ │ │ │ ├── chrono v0.4.31 (*) +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── libp2p v0.51.3 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ ├── pin-project v1.1.3 (*) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── sc-utils v10.0.0 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ └── wasm-timer v0.2.5 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ └── sp-state-machine v0.31.0 (*) +│ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ ├── sc-client-db v0.31.0 (*) +│ │ │ ├── sc-consensus v0.29.0 (*) +│ │ │ ├── sc-executor v0.28.0 (*) +│ │ │ ├── sc-informant v0.29.0 +│ │ │ │ ├── ansi_term v0.12.1 +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ │ ├── sc-network v0.30.0 (*) +│ │ │ │ ├── sc-network-common v0.29.0 (*) +│ │ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ │ └── sp-runtime v27.0.0 (*) +│ │ │ ├── sc-keystore v21.0.0 (*) +│ │ │ ├── sc-network v0.30.0 (*) +│ │ │ ├── sc-network-bitswap v0.29.0 +│ │ │ │ ├── async-channel v1.9.0 (*) +│ │ │ │ ├── cid v0.9.0 +│ │ │ │ │ ├── core2 v0.4.0 (*) +│ │ │ │ │ ├── multibase v0.9.1 (*) +│ │ │ │ │ ├── multihash v0.17.0 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ └── unsigned-varint v0.7.2 (*) +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── prost v0.11.9 +│ │ │ │ │ ├── bytes v1.5.0 +│ │ │ │ │ └── prost-derive v0.11.9 (proc-macro) (*) +│ │ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ │ ├── sc-network v0.30.0 (*) +│ │ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ └── unsigned-varint v0.7.2 (*) +│ │ │ │ [build-dependencies] +│ │ │ │ └── prost-build v0.11.9 (*) +│ │ │ ├── sc-network-common v0.29.0 (*) +│ │ │ ├── sc-network-light v0.29.0 +│ │ │ │ ├── array-bytes v6.1.0 +│ │ │ │ ├── async-channel v1.9.0 (*) +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── libp2p-identity v0.1.3 (*) +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── prost v0.11.9 (*) +│ │ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ │ ├── sc-network v0.30.0 (*) +│ │ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ [build-dependencies] +│ │ │ │ └── prost-build v0.11.9 (*) +│ │ │ ├── sc-network-sync v0.29.0 +│ │ │ │ ├── array-bytes v6.1.0 +│ │ │ │ ├── async-channel v1.9.0 (*) +│ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ ├── fork-tree v10.0.0 +│ │ │ │ │ └── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ ├── libp2p v0.51.3 (*) +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── mockall v0.11.4 (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── prost v0.11.9 (*) +│ │ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ │ ├── sc-consensus v0.29.0 (*) +│ │ │ │ ├── sc-network v0.30.0 (*) +│ │ │ │ ├── sc-network-common v0.29.0 (*) +│ │ │ │ ├── sc-utils v10.0.0 (*) +│ │ │ │ ├── schnellru v0.2.1 (*) +│ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ ├── sp-arithmetic v19.0.0 (*) +│ │ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ │ ├── sp-consensus v0.28.0 (*) +│ │ │ │ ├── sp-consensus-grandpa v9.0.0 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ ├── substrate-prometheus-endpoint v0.15.0 (*) +│ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ [build-dependencies] +│ │ │ │ └── prost-build v0.11.9 (*) +│ │ │ ├── sc-network-transactions v0.29.0 +│ │ │ │ ├── array-bytes v6.1.0 +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── libp2p v0.51.3 (*) +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── sc-network v0.30.0 (*) +│ │ │ │ ├── sc-network-common v0.29.0 (*) +│ │ │ │ ├── sc-utils v10.0.0 (*) +│ │ │ │ ├── sp-consensus v0.28.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ └── substrate-prometheus-endpoint v0.15.0 (*) +│ │ │ ├── sc-rpc v25.0.0 +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── jsonrpsee v0.16.3 (*) +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ ├── sc-block-builder v0.29.0 (*) +│ │ │ │ ├── sc-chain-spec v23.0.0 (*) +│ │ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ │ ├── sc-rpc-api v0.29.0 +│ │ │ │ │ ├── jsonrpsee v0.16.3 (*) +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── sc-chain-spec v23.0.0 (*) +│ │ │ │ │ ├── sc-transaction-pool-api v24.0.0 (*) +│ │ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ ├── sp-rpc v22.0.0 +│ │ │ │ │ │ ├── rustc-hash v1.1.0 +│ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ └── sp-core v24.0.0 (*) +│ │ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ │ ├── sp-version v25.0.0 (*) +│ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ ├── sc-tracing v24.0.0 +│ │ │ │ │ ├── ansi_term v0.12.1 +│ │ │ │ │ ├── atty v0.2.14 +│ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ ├── chrono v0.4.31 (*) +│ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ │ ├── regex v1.10.2 (*) +│ │ │ │ │ ├── rustc-hash v1.1.0 +│ │ │ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ │ │ ├── sc-tracing-proc-macro v9.0.0 (proc-macro) +│ │ │ │ │ │ ├── proc-macro-crate v1.1.3 (*) +│ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ └── syn v2.0.38 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ ├── sp-rpc v22.0.0 (*) +│ │ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ │ ├── sp-tracing v13.0.0 (*) +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ ├── tracing v0.1.40 (*) +│ │ │ │ │ ├── tracing-log v0.1.4 (*) +│ │ │ │ │ └── tracing-subscriber v0.2.25 (*) +│ │ │ │ ├── sc-transaction-pool-api v24.0.0 (*) +│ │ │ │ ├── sc-utils v10.0.0 (*) +│ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-keystore v0.30.0 (*) +│ │ │ │ ├── sp-offchain v22.0.0 +│ │ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ └── sp-runtime v27.0.0 (*) +│ │ │ │ ├── sp-rpc v22.0.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ ├── sp-session v23.0.0 +│ │ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ │ ├── sp-keystore v0.30.0 (*) +│ │ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ │ ├── sp-staking v22.0.0 (*) +│ │ │ │ │ └── sp-std v11.0.0 +│ │ │ │ ├── sp-statement-store v6.0.0 (*) +│ │ │ │ ├── sp-version v25.0.0 (*) +│ │ │ │ └── tokio v1.33.0 (*) +│ │ │ ├── sc-rpc-server v9.0.0 +│ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ ├── jsonrpsee v0.16.3 (*) +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ ├── substrate-prometheus-endpoint v0.15.0 (*) +│ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ ├── tower v0.4.13 (*) +│ │ │ │ └── tower-http v0.4.4 +│ │ │ │ ├── bitflags v2.4.1 +│ │ │ │ ├── bytes v1.5.0 +│ │ │ │ ├── futures-core v0.3.28 +│ │ │ │ ├── futures-util v0.3.28 (*) +│ │ │ │ ├── http v0.2.9 (*) +│ │ │ │ ├── http-body v0.4.5 (*) +│ │ │ │ ├── http-range-header v0.3.1 +│ │ │ │ ├── pin-project-lite v0.2.13 +│ │ │ │ ├── tower-layer v0.3.2 +│ │ │ │ └── tower-service v0.3.2 +│ │ │ ├── sc-rpc-spec-v2 v0.30.0 +│ │ │ │ ├── array-bytes v6.1.0 +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── futures-util v0.3.28 (*) +│ │ │ │ ├── hex v0.4.3 +│ │ │ │ ├── jsonrpsee v0.16.3 (*) +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ ├── sc-chain-spec v23.0.0 (*) +│ │ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ │ ├── sc-transaction-pool-api v24.0.0 (*) +│ │ │ │ ├── sc-utils v10.0.0 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ ├── sp-version v25.0.0 (*) +│ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ └── tokio-stream v0.1.14 (*) +│ │ │ ├── sc-sysinfo v23.0.0 +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── libc v0.2.149 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ ├── rand_pcg v0.3.1 (*) +│ │ │ │ ├── regex v1.10.2 (*) +│ │ │ │ ├── sc-telemetry v11.0.0 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-io v26.0.0 (*) +│ │ │ │ └── sp-std v11.0.0 +│ │ │ ├── sc-telemetry v11.0.0 (*) +│ │ │ ├── sc-tracing v24.0.0 (*) +│ │ │ ├── sc-transaction-pool v24.0.0 +│ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── futures-timer v3.0.2 +│ │ │ │ ├── linked-hash-map v0.5.6 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ │ ├── sc-client-api v24.0.0 (*) +│ │ │ │ ├── sc-transaction-pool-api v24.0.0 (*) +│ │ │ │ ├── sc-utils v10.0.0 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ ├── sp-tracing v13.0.0 (*) +│ │ │ │ ├── sp-transaction-pool v22.0.0 +│ │ │ │ │ ├── sp-api v22.0.0 (*) +│ │ │ │ │ └── sp-runtime v27.0.0 (*) +│ │ │ │ ├── substrate-prometheus-endpoint v0.15.0 (*) +│ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ ├── sc-transaction-pool-api v24.0.0 (*) +│ │ │ ├── sc-utils v10.0.0 (*) +│ │ │ ├── serde v1.0.189 (*) +│ │ │ ├── serde_json v1.0.107 (*) +│ │ │ ├── sp-api v22.0.0 (*) +│ │ │ ├── sp-blockchain v24.0.0 (*) +│ │ │ ├── sp-consensus v0.28.0 (*) +│ │ │ ├── sp-core v24.0.0 (*) +│ │ │ ├── sp-externalities v0.22.0 (*) +│ │ │ ├── sp-keystore v0.30.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ ├── sp-session v23.0.0 (*) +│ │ │ ├── sp-state-machine v0.31.0 (*) +│ │ │ ├── sp-storage v16.0.0 (*) +│ │ │ ├── sp-transaction-pool v22.0.0 (*) +│ │ │ ├── sp-transaction-storage-proof v22.0.0 +│ │ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ ├── sp-core v24.0.0 (*) +│ │ │ │ ├── sp-inherents v22.0.0 (*) +│ │ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ └── sp-trie v25.0.0 (*) +│ │ │ ├── sp-trie v25.0.0 (*) +│ │ │ ├── sp-version v25.0.0 (*) +│ │ │ ├── static_init v1.0.3 +│ │ │ │ ├── bitflags v1.3.2 +│ │ │ │ ├── parking_lot v0.11.2 (*) +│ │ │ │ ├── parking_lot_core v0.8.6 (*) +│ │ │ │ └── static_init_macro v1.0.2 (proc-macro) +│ │ │ │ ├── memchr v2.6.4 +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ [build-dependencies] +│ │ │ │ └── cfg_aliases v0.1.1 +│ │ │ │ [build-dependencies] +│ │ │ │ └── cfg_aliases v0.1.1 +│ │ │ ├── substrate-prometheus-endpoint v0.15.0 (*) +│ │ │ ├── tempfile v3.8.0 (*) +│ │ │ ├── thiserror v1.0.50 (*) +│ │ │ ├── tokio v1.33.0 (*) +│ │ │ ├── tracing v0.1.40 (*) +│ │ │ └── tracing-futures v0.2.5 +│ │ │ ├── pin-project v1.1.3 (*) +│ │ │ └── tracing v0.1.40 (*) +│ │ ├── sc-telemetry v11.0.0 (*) +│ │ ├── sc-tracing v24.0.0 (*) +│ │ ├── sc-utils v10.0.0 (*) +│ │ ├── serde v1.0.189 (*) +│ │ ├── serde_json v1.0.107 (*) +│ │ ├── sp-blockchain v24.0.0 (*) +│ │ ├── sp-core v24.0.0 (*) +│ │ ├── sp-keyring v27.0.0 +│ │ │ ├── lazy_static v1.4.0 +│ │ │ ├── sp-core v24.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ └── strum v0.24.1 +│ │ │ └── strum_macros v0.24.3 (proc-macro) +│ │ │ ├── heck v0.4.1 +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ ├── rustversion v1.0.14 (proc-macro) +│ │ │ └── syn v1.0.109 (*) +│ │ ├── sp-keystore v0.30.0 (*) +│ │ ├── sp-panic-handler v11.0.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ ├── sp-version v25.0.0 (*) +│ │ ├── thiserror v1.0.50 (*) +│ │ ├── tiny-bip39 v1.0.0 (*) +│ │ └── tokio v1.33.0 (*) +│ ├── sc-client-api v24.0.0 (*) +│ ├── sc-client-db v0.31.0 (*) +│ ├── sc-executor v0.28.0 (*) +│ ├── sc-service v0.31.0 (*) +│ ├── sc-sysinfo v23.0.0 (*) +│ ├── serde v1.0.189 (*) +│ ├── serde_json v1.0.107 (*) +│ ├── sp-api v22.0.0 (*) +│ ├── sp-blockchain v24.0.0 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-database v8.0.0 (*) +│ ├── sp-externalities v0.22.0 (*) +│ ├── sp-inherents v22.0.0 (*) +│ ├── sp-io v26.0.0 (*) +│ ├── sp-keystore v0.30.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ ├── sp-state-machine v0.31.0 (*) +│ ├── sp-storage v16.0.0 (*) +│ ├── sp-trie v25.0.0 (*) +│ ├── sp-wasm-interface v17.0.0 (*) +│ ├── thiserror v1.0.50 (*) +│ └── thousands v0.2.0 +├── frame-system v24.0.0 (*) +├── futures v0.3.28 (*) +├── jsonrpsee v0.16.3 (*) +├── pallet-transaction-payment v24.0.0 +│ ├── frame-support v24.0.0 (*) +│ ├── frame-system v24.0.0 (*) +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── scale-info v2.10.0 (*) +│ ├── serde v1.0.189 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-io v26.0.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ └── sp-std v11.0.0 +├── pallet-transaction-payment-rpc v26.0.0 +│ ├── jsonrpsee v0.16.3 (*) +│ ├── pallet-transaction-payment-rpc-runtime-api v24.0.0 +│ │ ├── pallet-transaction-payment v24.0.0 (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── sp-api v22.0.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ └── sp-weights v23.0.0 (*) +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── sp-api v22.0.0 (*) +│ ├── sp-blockchain v24.0.0 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-rpc v22.0.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ └── sp-weights v23.0.0 (*) +├── runtime-chronicle v4.0.0 (/Users/ryan/code/chronicle/node/runtime-chronicle) +│ ├── common v0.7.5 (/Users/ryan/code/chronicle/crates/common) +│ │ ├── anyhow v1.0.75 +│ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ ├── chrono v0.4.31 (*) +│ │ ├── custom_error v1.9.2 +│ │ ├── futures v0.3.28 (*) +│ │ ├── glob v0.3.1 +│ │ ├── hashbrown v0.13.2 (*) +│ │ ├── hex v0.4.3 +│ │ ├── iref v2.2.3 +│ │ │ ├── pct-str v1.2.0 +│ │ │ │ └── utf8-decode v1.0.1 +│ │ │ └── smallvec v1.11.1 +│ │ ├── iref-enum v2.1.0 (proc-macro) +│ │ │ ├── iref v2.2.3 (*) +│ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ ├── quote v1.0.33 (*) +│ │ │ └── syn v1.0.109 (*) +│ │ ├── json-ld v0.14.2 +│ │ │ ├── contextual v0.1.6 +│ │ │ ├── futures v0.3.28 (*) +│ │ │ ├── json-ld-compaction v0.14.2 +│ │ │ │ ├── contextual v0.1.6 +│ │ │ │ ├── derivative v2.2.0 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ ├── iref v2.2.3 (*) +│ │ │ │ ├── json-ld-context-processing v0.14.2 +│ │ │ │ │ ├── contextual v0.1.6 +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── iref v2.2.3 (*) +│ │ │ │ │ ├── json-ld-core v0.14.2 +│ │ │ │ │ │ ├── contextual v0.1.6 +│ │ │ │ │ │ ├── derivative v2.2.0 (proc-macro) (*) +│ │ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ │ ├── hashbrown v0.13.2 (*) +│ │ │ │ │ │ ├── iref v2.2.3 (*) +│ │ │ │ │ │ ├── json-ld-syntax v0.14.2 +│ │ │ │ │ │ │ ├── contextual v0.1.6 +│ │ │ │ │ │ │ ├── decoded-char v0.1.1 +│ │ │ │ │ │ │ ├── derivative v2.2.0 (proc-macro) (*) +│ │ │ │ │ │ │ ├── hashbrown v0.13.2 (*) +│ │ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ │ ├── iref v2.2.3 (*) +│ │ │ │ │ │ │ ├── json-syntax v0.9.6 +│ │ │ │ │ │ │ │ ├── contextual v0.1.6 +│ │ │ │ │ │ │ │ ├── decoded-char v0.1.1 +│ │ │ │ │ │ │ │ ├── hashbrown v0.12.3 (*) +│ │ │ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ │ │ ├── json-number v0.4.6 +│ │ │ │ │ │ │ │ │ ├── lexical v6.1.1 +│ │ │ │ │ │ │ │ │ │ └── lexical-core v0.8.5 +│ │ │ │ │ │ │ │ │ │ ├── lexical-parse-float v0.8.5 +│ │ │ │ │ │ │ │ │ │ │ ├── lexical-parse-integer v0.8.6 +│ │ │ │ │ │ │ │ │ │ │ │ ├── lexical-util v0.8.5 +│ │ │ │ │ │ │ │ │ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ │ │ │ │ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ │ │ │ │ │ │ │ ├── lexical-util v0.8.5 (*) +│ │ │ │ │ │ │ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ │ │ │ │ │ │ ├── lexical-parse-integer v0.8.6 (*) +│ │ │ │ │ │ │ │ │ │ ├── lexical-util v0.8.5 (*) +│ │ │ │ │ │ │ │ │ │ ├── lexical-write-float v0.8.5 +│ │ │ │ │ │ │ │ │ │ │ ├── lexical-util v0.8.5 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── lexical-write-integer v0.8.5 +│ │ │ │ │ │ │ │ │ │ │ │ ├── lexical-util v0.8.5 (*) +│ │ │ │ │ │ │ │ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ │ │ │ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ │ │ │ │ │ │ └── lexical-write-integer v0.8.5 (*) +│ │ │ │ │ │ │ │ │ ├── ryu-js v0.2.2 +│ │ │ │ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ │ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ │ │ │ │ ├── locspan v0.7.16 +│ │ │ │ │ │ │ │ │ ├── contextual v0.1.6 +│ │ │ │ │ │ │ │ │ └── hashbrown v0.13.2 (*) +│ │ │ │ │ │ │ │ ├── locspan-derive v0.6.0 (proc-macro) +│ │ │ │ │ │ │ │ │ ├── proc-macro-error v1.0.4 (*) +│ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ │ │ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ │ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ │ │ │ │ ├── ryu-js v0.2.2 +│ │ │ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ │ │ │ │ ├── smallstr v0.3.0 +│ │ │ │ │ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ │ │ │ ├── langtag v0.3.4 +│ │ │ │ │ │ │ ├── locspan v0.7.16 (*) +│ │ │ │ │ │ │ ├── locspan-derive v0.6.0 (proc-macro) (*) +│ │ │ │ │ │ │ ├── rdf-types v0.14.9 +│ │ │ │ │ │ │ │ ├── contextual v0.1.6 +│ │ │ │ │ │ │ │ ├── iref v2.2.3 (*) +│ │ │ │ │ │ │ │ ├── langtag v0.3.4 +│ │ │ │ │ │ │ │ ├── locspan v0.7.16 (*) +│ │ │ │ │ │ │ │ ├── locspan-derive v0.6.0 (proc-macro) (*) +│ │ │ │ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ │ │ ├── json-syntax v0.9.6 (*) +│ │ │ │ │ │ ├── langtag v0.3.4 +│ │ │ │ │ │ ├── locspan v0.7.16 (*) +│ │ │ │ │ │ ├── locspan-derive v0.6.0 (proc-macro) (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── mime v0.3.17 +│ │ │ │ │ │ ├── mown v0.2.2 +│ │ │ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ │ │ ├── permutohedron v0.2.4 +│ │ │ │ │ │ ├── pretty_dtoa v0.3.0 +│ │ │ │ │ │ │ └── ryu_floating_decimal v0.1.0 +│ │ │ │ │ │ ├── rdf-types v0.14.9 (*) +│ │ │ │ │ │ ├── ryu-js v0.2.2 +│ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ ├── static-iref v2.0.0 (proc-macro) +│ │ │ │ │ │ │ └── iref v2.2.3 (*) +│ │ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ │ ├── json-ld-syntax v0.14.2 (*) +│ │ │ │ │ ├── locspan v0.7.16 (*) +│ │ │ │ │ ├── mown v0.2.2 +│ │ │ │ │ ├── rdf-types v0.14.9 (*) +│ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ ├── json-ld-core v0.14.2 (*) +│ │ │ │ ├── json-ld-expansion v0.14.2 +│ │ │ │ │ ├── contextual v0.1.6 +│ │ │ │ │ ├── derivative v2.2.0 (proc-macro) (*) +│ │ │ │ │ ├── futures v0.3.28 (*) +│ │ │ │ │ ├── iref v2.2.3 (*) +│ │ │ │ │ ├── json-ld-context-processing v0.14.2 (*) +│ │ │ │ │ ├── json-ld-core v0.14.2 (*) +│ │ │ │ │ ├── json-ld-syntax v0.14.2 (*) +│ │ │ │ │ ├── json-syntax v0.9.6 (*) +│ │ │ │ │ ├── langtag v0.3.4 +│ │ │ │ │ ├── locspan v0.7.16 (*) +│ │ │ │ │ ├── mown v0.2.2 +│ │ │ │ │ ├── rdf-types v0.14.9 (*) +│ │ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ │ ├── json-ld-syntax v0.14.2 (*) +│ │ │ │ ├── json-syntax v0.9.6 (*) +│ │ │ │ ├── langtag v0.3.4 +│ │ │ │ ├── locspan v0.7.16 (*) +│ │ │ │ ├── mown v0.2.2 +│ │ │ │ ├── rdf-types v0.14.9 (*) +│ │ │ │ └── thiserror v1.0.50 (*) +│ │ │ ├── json-ld-context-processing v0.14.2 (*) +│ │ │ ├── json-ld-core v0.14.2 (*) +│ │ │ ├── json-ld-expansion v0.14.2 (*) +│ │ │ ├── json-ld-syntax v0.14.2 (*) +│ │ │ ├── json-syntax v0.9.6 (*) +│ │ │ ├── locspan v0.7.16 (*) +│ │ │ ├── rdf-types v0.14.9 (*) +│ │ │ └── thiserror v1.0.50 (*) +│ │ ├── json-syntax v0.9.6 (*) +│ │ ├── k256 v0.11.6 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── ecdsa v0.14.8 +│ │ │ │ ├── der v0.6.1 +│ │ │ │ │ ├── const-oid v0.9.5 +│ │ │ │ │ ├── pem-rfc7468 v0.6.0 +│ │ │ │ │ │ └── base64ct v1.6.0 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── elliptic-curve v0.12.3 +│ │ │ │ │ ├── base16ct v0.1.1 +│ │ │ │ │ ├── crypto-bigint v0.4.9 +│ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ │ ├── der v0.6.1 (*) +│ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ ├── ff v0.12.1 +│ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ ├── group v0.12.1 +│ │ │ │ │ │ ├── ff v0.12.1 (*) +│ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ └── subtle v2.4.1 +│ │ │ │ │ ├── pem-rfc7468 v0.6.0 (*) +│ │ │ │ │ ├── pkcs8 v0.9.0 +│ │ │ │ │ │ ├── der v0.6.1 (*) +│ │ │ │ │ │ └── spki v0.6.0 +│ │ │ │ │ │ ├── base64ct v1.6.0 +│ │ │ │ │ │ └── der v0.6.1 (*) +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ ├── sec1 v0.3.0 +│ │ │ │ │ │ ├── base16ct v0.1.1 +│ │ │ │ │ │ ├── der v0.6.1 (*) +│ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ ├── pkcs8 v0.9.0 (*) +│ │ │ │ │ │ ├── serdect v0.1.0 +│ │ │ │ │ │ │ ├── base16ct v0.1.1 +│ │ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ │ ├── serdect v0.1.0 (*) +│ │ │ │ │ ├── subtle v2.4.1 +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── rfc6979 v0.3.1 +│ │ │ │ │ ├── crypto-bigint v0.4.9 (*) +│ │ │ │ │ ├── hmac v0.12.1 (*) +│ │ │ │ │ └── zeroize v1.6.0 (*) +│ │ │ │ ├── serdect v0.1.0 (*) +│ │ │ │ └── signature v1.6.4 +│ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ └── rand_core v0.6.4 (*) +│ │ │ ├── elliptic-curve v0.12.3 (*) +│ │ │ ├── serdect v0.1.0 (*) +│ │ │ └── sha2 v0.10.8 (*) +│ │ ├── lazy_static v1.4.0 +│ │ ├── locspan v0.7.16 (*) +│ │ ├── macro-attr-2018 v3.0.0 +│ │ ├── mime v0.3.17 +│ │ ├── newtype-derive-2018 v0.2.1 +│ │ │ └── generics v0.5.0 +│ │ ├── opa v0.9.0 (https://github.com/tamasfe/opa-rs?rev=3cf7fea#3cf7fea8) +│ │ │ ├── anyhow v1.0.75 +│ │ │ ├── bytes v1.5.0 +│ │ │ ├── flate2 v1.0.28 (*) +│ │ │ ├── serde v1.0.189 (*) +│ │ │ ├── serde_json v1.0.107 (*) +│ │ │ ├── tar v0.4.40 +│ │ │ │ ├── filetime v0.2.22 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ └── libc v0.2.149 +│ │ │ │ ├── libc v0.2.149 +│ │ │ │ └── xattr v1.0.1 +│ │ │ │ └── libc v0.2.149 +│ │ │ ├── tempfile v3.8.0 (*) +│ │ │ ├── thiserror v1.0.50 (*) +│ │ │ ├── walkdir v2.4.0 (*) +│ │ │ ├── wasmtime v10.0.2 +│ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ ├── bincode v1.3.3 (*) +│ │ │ │ ├── bumpalo v3.14.0 +│ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ ├── fxprof-processed-profile v0.6.0 +│ │ │ │ │ ├── bitflags v2.4.1 +│ │ │ │ │ ├── debugid v0.8.0 +│ │ │ │ │ │ └── uuid v1.5.0 +│ │ │ │ │ │ ├── getrandom v0.2.10 (*) +│ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ └── wasm-bindgen v0.2.87 (*) +│ │ │ │ │ ├── fxhash v0.2.1 (*) +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ └── serde_json v1.0.107 (*) +│ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ ├── libc v0.2.149 +│ │ │ │ ├── log v0.4.20 +│ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ ├── once_cell v1.18.0 +│ │ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ │ ├── psm v0.1.21 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── serde_json v1.0.107 (*) +│ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ ├── wasmparser v0.107.0 +│ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ └── semver v1.0.20 +│ │ │ │ ├── wasmtime-cranelift v10.0.2 +│ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ ├── cranelift-codegen v0.97.2 +│ │ │ │ │ │ ├── bumpalo v3.14.0 +│ │ │ │ │ │ ├── cranelift-bforest v0.97.2 +│ │ │ │ │ │ │ └── cranelift-entity v0.97.2 +│ │ │ │ │ │ │ └── serde v1.0.189 (*) +│ │ │ │ │ │ ├── cranelift-codegen-shared v0.97.2 +│ │ │ │ │ │ ├── cranelift-control v0.97.2 +│ │ │ │ │ │ │ └── arbitrary v1.3.1 +│ │ │ │ │ │ ├── cranelift-entity v0.97.2 (*) +│ │ │ │ │ │ ├── gimli v0.27.3 (*) +│ │ │ │ │ │ ├── hashbrown v0.13.2 (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── regalloc2 v0.9.3 +│ │ │ │ │ │ │ ├── hashbrown v0.13.2 (*) +│ │ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ │ ├── rustc-hash v1.1.0 +│ │ │ │ │ │ │ ├── slice-group-by v0.3.1 +│ │ │ │ │ │ │ └── smallvec v1.11.1 +│ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ └── target-lexicon v0.12.12 +│ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ ├── cranelift-codegen-meta v0.97.2 +│ │ │ │ │ │ │ └── cranelift-codegen-shared v0.97.2 +│ │ │ │ │ │ └── cranelift-isle v0.97.2 +│ │ │ │ │ ├── cranelift-control v0.97.2 (*) +│ │ │ │ │ ├── cranelift-entity v0.97.2 (*) +│ │ │ │ │ ├── cranelift-frontend v0.97.2 +│ │ │ │ │ │ ├── cranelift-codegen v0.97.2 (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ └── target-lexicon v0.12.12 +│ │ │ │ │ ├── cranelift-native v0.97.2 +│ │ │ │ │ │ ├── cranelift-codegen v0.97.2 (*) +│ │ │ │ │ │ └── target-lexicon v0.12.12 +│ │ │ │ │ ├── cranelift-wasm v0.97.2 +│ │ │ │ │ │ ├── cranelift-codegen v0.97.2 (*) +│ │ │ │ │ │ ├── cranelift-entity v0.97.2 (*) +│ │ │ │ │ │ ├── cranelift-frontend v0.97.2 (*) +│ │ │ │ │ │ ├── itertools v0.10.5 (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── smallvec v1.11.1 +│ │ │ │ │ │ ├── wasmparser v0.107.0 (*) +│ │ │ │ │ │ └── wasmtime-types v10.0.2 +│ │ │ │ │ │ ├── cranelift-entity v0.97.2 (*) +│ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ └── wasmparser v0.107.0 (*) +│ │ │ │ │ ├── gimli v0.27.3 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ ├── wasmparser v0.107.0 (*) +│ │ │ │ │ ├── wasmtime-cranelift-shared v10.0.2 +│ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ ├── cranelift-codegen v0.97.2 (*) +│ │ │ │ │ │ ├── cranelift-control v0.97.2 (*) +│ │ │ │ │ │ ├── cranelift-native v0.97.2 (*) +│ │ │ │ │ │ ├── gimli v0.27.3 (*) +│ │ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ │ └── wasmtime-environ v10.0.2 +│ │ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ │ ├── cranelift-entity v0.97.2 (*) +│ │ │ │ │ │ ├── gimli v0.27.3 (*) +│ │ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ │ ├── thiserror v1.0.50 (*) +│ │ │ │ │ │ ├── wasmparser v0.107.0 (*) +│ │ │ │ │ │ └── wasmtime-types v10.0.2 (*) +│ │ │ │ │ └── wasmtime-environ v10.0.2 (*) +│ │ │ │ ├── wasmtime-environ v10.0.2 (*) +│ │ │ │ ├── wasmtime-jit v10.0.2 +│ │ │ │ │ ├── addr2line v0.19.0 (*) +│ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ ├── bincode v1.3.3 (*) +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── cpp_demangle v0.3.5 (*) +│ │ │ │ │ ├── gimli v0.27.3 (*) +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── object v0.30.4 (*) +│ │ │ │ │ ├── rustc-demangle v0.1.23 +│ │ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ │ ├── target-lexicon v0.12.12 +│ │ │ │ │ ├── wasmtime-environ v10.0.2 (*) +│ │ │ │ │ ├── wasmtime-jit-icache-coherence v10.0.2 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ └── wasmtime-runtime v10.0.2 +│ │ │ │ │ ├── anyhow v1.0.75 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── indexmap v1.9.3 (*) +│ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ ├── log v0.4.20 +│ │ │ │ │ ├── mach v0.3.2 (*) +│ │ │ │ │ ├── memfd v0.6.4 (*) +│ │ │ │ │ ├── memoffset v0.8.0 (*) +│ │ │ │ │ ├── paste v1.0.14 (proc-macro) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── rustix v0.37.26 +│ │ │ │ │ │ ├── bitflags v1.3.2 +│ │ │ │ │ │ ├── errno v0.3.5 (*) +│ │ │ │ │ │ ├── io-lifetimes v1.0.11 (*) +│ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ ├── sptr v0.3.2 +│ │ │ │ │ ├── wasmtime-asm-macros v10.0.2 +│ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ ├── wasmtime-environ v10.0.2 (*) +│ │ │ │ │ └── wasmtime-jit-debug v10.0.2 +│ │ │ │ │ └── once_cell v1.18.0 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── cc v1.0.83 (*) +│ │ │ │ └── wasmtime-runtime v10.0.2 (*) +│ │ │ └── which v4.4.2 (*) +│ │ ├── openssl v0.10.57 +│ │ │ ├── bitflags v2.4.1 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── foreign-types v0.3.2 +│ │ │ │ └── foreign-types-shared v0.1.1 +│ │ │ ├── libc v0.2.149 +│ │ │ ├── once_cell v1.18.0 +│ │ │ ├── openssl-macros v0.1.1 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ └── openssl-sys v0.9.93 +│ │ │ └── libc v0.2.149 +│ │ │ [build-dependencies] +│ │ │ ├── cc v1.0.83 (*) +│ │ │ ├── pkg-config v0.3.27 +│ │ │ └── vcpkg v0.2.15 +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── percent-encoding v2.3.0 +│ │ ├── pkcs8 v0.10.2 +│ │ │ ├── der v0.7.8 (*) +│ │ │ └── spki v0.7.2 (*) +│ │ ├── r2d2 v0.8.10 +│ │ │ ├── log v0.4.20 +│ │ │ ├── parking_lot v0.12.1 (*) +│ │ │ └── scheduled-thread-pool v0.2.7 +│ │ │ └── parking_lot v0.12.1 (*) +│ │ ├── rand v0.8.5 (*) +│ │ ├── rand_core v0.6.4 (*) +│ │ ├── rdf-types v0.14.9 (*) +│ │ ├── reqwest v0.11.22 +│ │ │ ├── base64 v0.21.5 +│ │ │ ├── bytes v1.5.0 +│ │ │ ├── encoding_rs v0.8.33 +│ │ │ │ └── cfg-if v1.0.0 +│ │ │ ├── futures-core v0.3.28 +│ │ │ ├── futures-util v0.3.28 (*) +│ │ │ ├── h2 v0.3.21 (*) +│ │ │ ├── http v0.2.9 (*) +│ │ │ ├── http-body v0.4.5 (*) +│ │ │ ├── hyper v0.14.27 (*) +│ │ │ ├── hyper-tls v0.5.0 +│ │ │ │ ├── bytes v1.5.0 +│ │ │ │ ├── hyper v0.14.27 (*) +│ │ │ │ ├── native-tls v0.2.11 +│ │ │ │ │ ├── lazy_static v1.4.0 +│ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ ├── security-framework v2.9.2 +│ │ │ │ │ │ ├── bitflags v1.3.2 +│ │ │ │ │ │ ├── core-foundation v0.9.3 (*) +│ │ │ │ │ │ ├── core-foundation-sys v0.8.4 +│ │ │ │ │ │ ├── libc v0.2.149 +│ │ │ │ │ │ └── security-framework-sys v2.9.1 +│ │ │ │ │ │ ├── core-foundation-sys v0.8.4 +│ │ │ │ │ │ └── libc v0.2.149 +│ │ │ │ │ ├── security-framework-sys v2.9.1 (*) +│ │ │ │ │ └── tempfile v3.8.0 (*) +│ │ │ │ ├── tokio v1.33.0 (*) +│ │ │ │ └── tokio-native-tls v0.3.1 +│ │ │ │ ├── native-tls v0.2.11 (*) +│ │ │ │ └── tokio v1.33.0 (*) +│ │ │ ├── ipnet v2.9.0 +│ │ │ ├── log v0.4.20 +│ │ │ ├── mime v0.3.17 +│ │ │ ├── native-tls v0.2.11 (*) +│ │ │ ├── once_cell v1.18.0 +│ │ │ ├── percent-encoding v2.3.0 +│ │ │ ├── pin-project-lite v0.2.13 +│ │ │ ├── serde v1.0.189 (*) +│ │ │ ├── serde_urlencoded v0.7.1 +│ │ │ │ ├── form_urlencoded v1.2.0 (*) +│ │ │ │ ├── itoa v1.0.9 +│ │ │ │ ├── ryu v1.0.15 +│ │ │ │ └── serde v1.0.189 (*) +│ │ │ ├── system-configuration v0.5.1 (*) +│ │ │ ├── tokio v1.33.0 (*) +│ │ │ ├── tokio-native-tls v0.3.1 (*) +│ │ │ ├── tower-service v0.3.2 +│ │ │ └── url v2.4.1 (*) +│ │ ├── rust-embed v6.8.1 +│ │ │ ├── rust-embed-impl v6.8.1 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ ├── rust-embed-utils v7.8.1 +│ │ │ │ │ ├── globset v0.4.13 (*) +│ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ └── walkdir v2.4.0 (*) +│ │ │ │ ├── syn v2.0.38 (*) +│ │ │ │ └── walkdir v2.4.0 (*) +│ │ │ ├── rust-embed-utils v7.8.1 +│ │ │ │ ├── globset v0.4.13 (*) +│ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ └── walkdir v2.4.0 (*) +│ │ │ └── walkdir v2.4.0 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── serde v1.0.189 (*) +│ │ ├── serde_derive v1.0.189 (proc-macro) (*) +│ │ ├── serde_json v1.0.107 (*) +│ │ ├── static-iref v2.0.0 (proc-macro) (*) +│ │ ├── thiserror v1.0.50 (*) +│ │ ├── tracing v0.1.40 (*) +│ │ ├── url v2.4.1 (*) +│ │ └── uuid v1.5.0 (*) +│ │ [build-dependencies] +│ │ ├── glob v0.3.1 +│ │ ├── lazy_static v1.4.0 +│ │ └── serde_json v1.0.107 (*) +│ ├── frame-executive v24.0.0 +│ │ ├── frame-support v24.0.0 (*) +│ │ ├── frame-system v24.0.0 (*) +│ │ ├── frame-try-runtime v0.30.0 +│ │ │ ├── frame-support v24.0.0 (*) +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── sp-api v22.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ └── sp-std v11.0.0 +│ │ ├── log v0.4.20 +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── sp-core v24.0.0 (*) +│ │ ├── sp-io v26.0.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ ├── sp-std v11.0.0 +│ │ └── sp-tracing v13.0.0 (*) +│ ├── frame-support v24.0.0 (*) +│ ├── frame-system v24.0.0 (*) +│ ├── frame-system-rpc-runtime-api v22.0.0 +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ └── sp-api v22.0.0 (*) +│ ├── frame-try-runtime v0.30.0 (*) +│ ├── pallet-aura v23.0.0 +│ │ ├── frame-support v24.0.0 (*) +│ │ ├── frame-system v24.0.0 (*) +│ │ ├── log v0.4.20 +│ │ ├── pallet-timestamp v23.0.0 +│ │ │ ├── docify v0.2.4 (*) +│ │ │ ├── frame-support v24.0.0 (*) +│ │ │ ├── frame-system v24.0.0 (*) +│ │ │ ├── log v0.4.20 +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ ├── sp-inherents v22.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ ├── sp-std v11.0.0 +│ │ │ ├── sp-storage v16.0.0 (*) +│ │ │ └── sp-timestamp v22.0.0 +│ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── sp-inherents v22.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ ├── sp-std v11.0.0 +│ │ │ └── thiserror v1.0.50 (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── sp-application-crypto v26.0.0 (*) +│ │ ├── sp-consensus-aura v0.28.0 +│ │ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ ├── sp-api v22.0.0 (*) +│ │ │ ├── sp-application-crypto v26.0.0 (*) +│ │ │ ├── sp-consensus-slots v0.28.0 +│ │ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ │ ├── scale-info v2.10.0 (*) +│ │ │ │ ├── serde v1.0.189 (*) +│ │ │ │ ├── sp-std v11.0.0 +│ │ │ │ └── sp-timestamp v22.0.0 (*) +│ │ │ ├── sp-inherents v22.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ ├── sp-std v11.0.0 +│ │ │ └── sp-timestamp v22.0.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ └── sp-std v11.0.0 +│ ├── pallet-balances v24.0.0 +│ │ ├── frame-support v24.0.0 (*) +│ │ ├── frame-system v24.0.0 (*) +│ │ ├── log v0.4.20 +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ └── sp-std v11.0.0 +│ ├── pallet-chronicle v0.7.5 (/Users/ryan/code/chronicle/crates/pallet-chronicle) +│ │ ├── common v0.7.5 (/Users/ryan/code/chronicle/crates/common) (*) +│ │ ├── frame-benchmarking v24.0.0 (*) +│ │ ├── frame-support v24.0.0 (*) +│ │ ├── frame-system v24.0.0 (*) +│ │ ├── macro-attr-2018 v3.0.0 +│ │ ├── newtype-derive-2018 v0.2.1 (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── sp-std v11.0.0 +│ │ ├── tracing v0.1.40 (*) +│ │ └── uuid v1.5.0 (*) +│ ├── pallet-grandpa v24.0.0 +│ │ ├── frame-support v24.0.0 (*) +│ │ ├── frame-system v24.0.0 (*) +│ │ ├── log v0.4.20 +│ │ ├── pallet-authorship v24.0.0 +│ │ │ ├── frame-support v24.0.0 (*) +│ │ │ ├── frame-system v24.0.0 (*) +│ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ └── sp-std v11.0.0 +│ │ ├── pallet-session v24.0.0 +│ │ │ ├── frame-support v24.0.0 (*) +│ │ │ ├── frame-system v24.0.0 (*) +│ │ │ ├── impl-trait-for-tuples v0.2.2 (proc-macro) (*) +│ │ │ ├── log v0.4.20 +│ │ │ ├── pallet-timestamp v23.0.0 (*) +│ │ │ ├── parity-scale-codec v3.6.5 (*) +│ │ │ ├── scale-info v2.10.0 (*) +│ │ │ ├── sp-core v24.0.0 (*) +│ │ │ ├── sp-io v26.0.0 (*) +│ │ │ ├── sp-runtime v27.0.0 (*) +│ │ │ ├── sp-session v23.0.0 (*) +│ │ │ ├── sp-staking v22.0.0 (*) +│ │ │ ├── sp-state-machine v0.31.0 (*) +│ │ │ ├── sp-std v11.0.0 +│ │ │ └── sp-trie v25.0.0 (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── sp-application-crypto v26.0.0 (*) +│ │ ├── sp-consensus-grandpa v9.0.0 (*) +│ │ ├── sp-core v24.0.0 (*) +│ │ ├── sp-io v26.0.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ ├── sp-session v23.0.0 (*) +│ │ ├── sp-staking v22.0.0 (*) +│ │ └── sp-std v11.0.0 +│ ├── pallet-sudo v24.0.0 +│ │ ├── docify v0.2.4 (*) +│ │ ├── frame-support v24.0.0 (*) +│ │ ├── frame-system v24.0.0 (*) +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── scale-info v2.10.0 (*) +│ │ ├── sp-io v26.0.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ └── sp-std v11.0.0 +│ ├── pallet-timestamp v23.0.0 (*) +│ ├── pallet-transaction-payment v24.0.0 (*) +│ ├── pallet-transaction-payment-rpc-runtime-api v24.0.0 (*) +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── scale-info v2.10.0 (*) +│ ├── sp-api v22.0.0 (*) +│ ├── sp-block-builder v22.0.0 (*) +│ ├── sp-consensus-aura v0.28.0 (*) +│ ├── sp-consensus-grandpa v9.0.0 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-inherents v22.0.0 (*) +│ ├── sp-offchain v22.0.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ ├── sp-session v23.0.0 (*) +│ ├── sp-std v11.0.0 +│ ├── sp-transaction-pool v22.0.0 (*) +│ └── sp-version v25.0.0 (*) +│ [build-dependencies] +│ └── substrate-wasm-builder v13.0.0 +│ ├── ansi_term v0.12.1 +│ ├── build-helper v0.1.1 +│ │ └── semver v0.6.0 +│ │ └── semver-parser v0.7.0 +│ ├── cargo_metadata v0.15.4 +│ │ ├── camino v1.1.6 +│ │ │ └── serde v1.0.189 (*) +│ │ ├── cargo-platform v0.1.4 +│ │ │ └── serde v1.0.189 (*) +│ │ ├── semver v1.0.20 (*) +│ │ ├── serde v1.0.189 (*) +│ │ ├── serde_json v1.0.107 (*) +│ │ └── thiserror v1.0.50 (*) +│ ├── filetime v0.2.22 (*) +│ ├── parity-wasm v0.45.0 +│ ├── sp-maybe-compressed-blob v8.0.0 (*) +│ ├── strum v0.24.1 +│ │ └── strum_macros v0.24.3 (proc-macro) (*) +│ ├── tempfile v3.8.0 (*) +│ ├── toml v0.7.8 (*) +│ ├── walkdir v2.4.0 (*) +│ └── wasm-opt v0.114.2 +│ ├── anyhow v1.0.75 +│ ├── libc v0.2.149 +│ ├── strum v0.24.1 (*) +│ ├── strum_macros v0.24.3 (proc-macro) (*) +│ ├── tempfile v3.8.0 (*) +│ ├── thiserror v1.0.50 (*) +│ ├── wasm-opt-cxx-sys v0.114.2 +│ │ ├── anyhow v1.0.75 +│ │ ├── cxx v1.0.109 +│ │ │ ├── cxxbridge-macro v1.0.109 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.69 (*) +│ │ │ │ ├── quote v1.0.33 (*) +│ │ │ │ └── syn v2.0.38 (*) +│ │ │ └── link-cplusplus v1.0.9 +│ │ │ [build-dependencies] +│ │ │ └── cc v1.0.83 (*) +│ │ │ [build-dependencies] +│ │ │ ├── cc v1.0.83 (*) +│ │ │ └── cxxbridge-flags v1.0.109 +│ │ └── wasm-opt-sys v0.114.2 +│ │ └── cxx v1.0.109 (*) +│ │ [build-dependencies] +│ │ ├── anyhow v1.0.75 +│ │ ├── cc v1.0.83 (*) +│ │ └── cxx-build v1.0.109 +│ │ ├── cc v1.0.83 (*) +│ │ ├── codespan-reporting v0.11.1 +│ │ │ ├── termcolor v1.3.0 +│ │ │ └── unicode-width v0.1.11 +│ │ ├── once_cell v1.18.0 +│ │ ├── proc-macro2 v1.0.69 (*) +│ │ ├── quote v1.0.33 (*) +│ │ ├── scratch v1.0.7 +│ │ └── syn v2.0.38 (*) +│ │ [build-dependencies] +│ │ ├── anyhow v1.0.75 +│ │ └── cxx-build v1.0.109 (*) +│ └── wasm-opt-sys v0.114.2 (*) +├── sc-basic-authorship v0.30.0 +│ ├── futures v0.3.28 (*) +│ ├── futures-timer v3.0.2 +│ ├── log v0.4.20 +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── sc-block-builder v0.29.0 (*) +│ ├── sc-client-api v24.0.0 (*) +│ ├── sc-proposer-metrics v0.15.0 +│ │ ├── log v0.4.20 +│ │ └── substrate-prometheus-endpoint v0.15.0 (*) +│ ├── sc-telemetry v11.0.0 (*) +│ ├── sc-transaction-pool-api v24.0.0 (*) +│ ├── sp-api v22.0.0 (*) +│ ├── sp-blockchain v24.0.0 (*) +│ ├── sp-consensus v0.28.0 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-inherents v22.0.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ └── substrate-prometheus-endpoint v0.15.0 (*) +├── sc-cli v0.32.0 (*) +├── sc-client-api v24.0.0 (*) +├── sc-consensus v0.29.0 (*) +├── sc-consensus-aura v0.30.0 +│ ├── async-trait v0.1.74 (proc-macro) (*) +│ ├── futures v0.3.28 (*) +│ ├── log v0.4.20 +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── sc-block-builder v0.29.0 (*) +│ ├── sc-client-api v24.0.0 (*) +│ ├── sc-consensus v0.29.0 (*) +│ ├── sc-consensus-slots v0.29.0 +│ │ ├── async-trait v0.1.74 (proc-macro) (*) +│ │ ├── futures v0.3.28 (*) +│ │ ├── futures-timer v3.0.2 +│ │ ├── log v0.4.20 +│ │ ├── parity-scale-codec v3.6.5 (*) +│ │ ├── sc-client-api v24.0.0 (*) +│ │ ├── sc-consensus v0.29.0 (*) +│ │ ├── sc-telemetry v11.0.0 (*) +│ │ ├── sp-arithmetic v19.0.0 (*) +│ │ ├── sp-blockchain v24.0.0 (*) +│ │ ├── sp-consensus v0.28.0 (*) +│ │ ├── sp-consensus-slots v0.28.0 (*) +│ │ ├── sp-core v24.0.0 (*) +│ │ ├── sp-inherents v22.0.0 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ └── sp-state-machine v0.31.0 (*) +│ ├── sc-telemetry v11.0.0 (*) +│ ├── sp-api v22.0.0 (*) +│ ├── sp-application-crypto v26.0.0 (*) +│ ├── sp-block-builder v22.0.0 (*) +│ ├── sp-blockchain v24.0.0 (*) +│ ├── sp-consensus v0.28.0 (*) +│ ├── sp-consensus-aura v0.28.0 (*) +│ ├── sp-consensus-slots v0.28.0 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-inherents v22.0.0 (*) +│ ├── sp-keystore v0.30.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ ├── substrate-prometheus-endpoint v0.15.0 (*) +│ └── thiserror v1.0.50 (*) +├── sc-consensus-grandpa v0.15.0 +│ ├── ahash v0.8.6 (*) +│ ├── array-bytes v6.1.0 +│ ├── async-trait v0.1.74 (proc-macro) (*) +│ ├── dyn-clone v1.0.14 +│ ├── finality-grandpa v0.16.2 (*) +│ ├── fork-tree v10.0.0 (*) +│ ├── futures v0.3.28 (*) +│ ├── futures-timer v3.0.2 +│ ├── log v0.4.20 +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── parking_lot v0.12.1 (*) +│ ├── rand v0.8.5 (*) +│ ├── sc-block-builder v0.29.0 (*) +│ ├── sc-chain-spec v23.0.0 (*) +│ ├── sc-client-api v24.0.0 (*) +│ ├── sc-consensus v0.29.0 (*) +│ ├── sc-network v0.30.0 (*) +│ ├── sc-network-common v0.29.0 (*) +│ ├── sc-network-gossip v0.30.0 +│ │ ├── ahash v0.8.6 (*) +│ │ ├── futures v0.3.28 (*) +│ │ ├── futures-timer v3.0.2 +│ │ ├── libp2p v0.51.3 (*) +│ │ ├── log v0.4.20 +│ │ ├── sc-network v0.30.0 (*) +│ │ ├── sc-network-common v0.29.0 (*) +│ │ ├── schnellru v0.2.1 (*) +│ │ ├── sp-runtime v27.0.0 (*) +│ │ ├── substrate-prometheus-endpoint v0.15.0 (*) +│ │ └── tracing v0.1.40 (*) +│ ├── sc-telemetry v11.0.0 (*) +│ ├── sc-transaction-pool-api v24.0.0 (*) +│ ├── sc-utils v10.0.0 (*) +│ ├── serde_json v1.0.107 (*) +│ ├── sp-api v22.0.0 (*) +│ ├── sp-application-crypto v26.0.0 (*) +│ ├── sp-arithmetic v19.0.0 (*) +│ ├── sp-blockchain v24.0.0 (*) +│ ├── sp-consensus v0.28.0 (*) +│ ├── sp-consensus-grandpa v9.0.0 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-keystore v0.30.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ ├── substrate-prometheus-endpoint v0.15.0 (*) +│ └── thiserror v1.0.50 (*) +├── sc-executor v0.28.0 (*) +├── sc-network v0.30.0 (*) +├── sc-offchain v25.0.0 +│ ├── array-bytes v6.1.0 +│ ├── bytes v1.5.0 +│ ├── fnv v1.0.7 +│ ├── futures v0.3.28 (*) +│ ├── futures-timer v3.0.2 +│ ├── hyper v0.14.27 (*) +│ ├── hyper-rustls v0.24.1 +│ │ ├── futures-util v0.3.28 (*) +│ │ ├── http v0.2.9 (*) +│ │ ├── hyper v0.14.27 (*) +│ │ ├── log v0.4.20 +│ │ ├── rustls v0.21.8 +│ │ │ ├── log v0.4.20 +│ │ │ ├── ring v0.17.5 (*) +│ │ │ ├── rustls-webpki v0.101.7 +│ │ │ │ ├── ring v0.17.5 (*) +│ │ │ │ └── untrusted v0.9.0 +│ │ │ └── sct v0.7.1 (*) +│ │ ├── rustls-native-certs v0.6.3 +│ │ │ ├── rustls-pemfile v1.0.3 +│ │ │ │ └── base64 v0.21.5 +│ │ │ └── security-framework v2.9.2 (*) +│ │ ├── tokio v1.33.0 (*) +│ │ └── tokio-rustls v0.24.1 +│ │ ├── rustls v0.21.8 (*) +│ │ └── tokio v1.33.0 (*) +│ ├── libp2p v0.51.3 (*) +│ ├── log v0.4.20 +│ ├── num_cpus v1.16.0 (*) +│ ├── once_cell v1.18.0 +│ ├── parity-scale-codec v3.6.5 (*) +│ ├── parking_lot v0.12.1 (*) +│ ├── rand v0.8.5 (*) +│ ├── sc-client-api v24.0.0 (*) +│ ├── sc-network v0.30.0 (*) +│ ├── sc-network-common v0.29.0 (*) +│ ├── sc-transaction-pool-api v24.0.0 (*) +│ ├── sc-utils v10.0.0 (*) +│ ├── sp-api v22.0.0 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-externalities v0.22.0 (*) +│ ├── sp-keystore v0.30.0 (*) +│ ├── sp-offchain v22.0.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ ├── threadpool v1.8.1 +│ │ └── num_cpus v1.16.0 (*) +│ └── tracing v0.1.40 (*) +├── sc-rpc-api v0.29.0 (*) +├── sc-service v0.31.0 (*) +├── sc-statement-store v6.0.0 +│ ├── log v0.4.20 +│ ├── parity-db v0.4.12 (*) +│ ├── parking_lot v0.12.1 (*) +│ ├── sc-client-api v24.0.0 (*) +│ ├── sc-keystore v21.0.0 (*) +│ ├── sp-api v22.0.0 (*) +│ ├── sp-blockchain v24.0.0 (*) +│ ├── sp-core v24.0.0 (*) +│ ├── sp-runtime v27.0.0 (*) +│ ├── sp-statement-store v6.0.0 (*) +│ ├── substrate-prometheus-endpoint v0.15.0 (*) +│ └── tokio v1.33.0 (*) +├── sc-telemetry v11.0.0 (*) +├── sc-transaction-pool v24.0.0 (*) +├── sc-transaction-pool-api v24.0.0 (*) +├── sp-api v22.0.0 (*) +├── sp-block-builder v22.0.0 (*) +├── sp-blockchain v24.0.0 (*) +├── sp-consensus-aura v0.28.0 (*) +├── sp-consensus-grandpa v9.0.0 (*) +├── sp-core v24.0.0 (*) +├── sp-inherents v22.0.0 (*) +├── sp-io v26.0.0 (*) +├── sp-keyring v27.0.0 (*) +├── sp-runtime v27.0.0 (*) +├── sp-timestamp v22.0.0 (*) +└── substrate-frame-rpc-system v24.0.0 + ├── frame-system-rpc-runtime-api v22.0.0 (*) + ├── futures v0.3.28 (*) + ├── jsonrpsee v0.16.3 (*) + ├── log v0.4.20 + ├── parity-scale-codec v3.6.5 (*) + ├── sc-rpc-api v0.29.0 (*) + ├── sc-transaction-pool-api v24.0.0 (*) + ├── sp-api v22.0.0 (*) + ├── sp-block-builder v22.0.0 (*) + ├── sp-blockchain v24.0.0 (*) + ├── sp-core v24.0.0 (*) + └── sp-runtime v27.0.0 (*) +[build-dependencies] +└── substrate-build-script-utils v8.0.0 diff --git a/node/runtime-chronicle/Cargo.toml b/node/runtime-chronicle/Cargo.toml new file mode 100644 index 000000000..60dd683db --- /dev/null +++ b/node/runtime-chronicle/Cargo.toml @@ -0,0 +1,114 @@ +[package] +name = "runtime-chronicle" +version = "4.0.0" +description = "A fresh FRAME-based Substrate node, ready for hacking." +authors = ["Substrate DevHub "] +homepage = "https://substrate.io/" +edition = "2021" +license = "MIT-0" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +parity-scale-codec = { version="3.6.5", default-features = false, features = ["derive"] } +scale-info = { version="2.10.0", default-features = false, features = ["derive"] } +pallet-aura = { version = "23.0.0", default-features=false } +pallet-balances = { version = "24.0.0", default-features = false } +frame-support = { version="24.0.0" , default-features=false } +pallet-grandpa = { version = "24.0.0", default-features = false } +pallet-sudo = { version = "24.0.0", default-features = false } +frame-system = { version= "24.0.0", default-features=false } +frame-try-runtime = { version= "0.30.0", default-features = false, optional=true} +pallet-timestamp = { version = "23.0.0", default-features=false } +pallet-transaction-payment = { version ="24.0.0",default-features=false} +frame-executive = { version = "24.0.0", default-features = false } +sp-api = { version = "22.0.0", default-features = false } +sp-block-builder = { version = "22.0.0", default-features = false } +sp-consensus-aura = { version = "0.28.0", default-features = false } +sp-consensus-grandpa = { version = "9.0.0", default-features = false} +sp-core = { version="24.0.0" ,default-features=false } +sp-inherents = { version = "22.0.0", default-features = false } +sp-offchain = { version = "22.0.0", default-features = false } +sp-runtime = { version="27.0.0", default-features = false} +sp-session = { version = "23.0.0", default-features = false} +sp-std = { version="11.0.0", default-features = false} +sp-transaction-pool = { version = "22.0.0", default-features = false} +sp-version = { version = "25.0.0", default-features = false} + +# Used for the node template's RPCs +frame-system-rpc-runtime-api = { version = "22.0.0", default-features = false} +pallet-transaction-payment-rpc-runtime-api = { version = "24.0.0", default-features = false} + +# Used for runtime benchmarking +frame-benchmarking = { version = "24.0.0", default-features = false, optional=true} +frame-system-benchmarking = { version = "24.0.0", default-features = false, optional=true} + +# Local Dependencies +pallet-chronicle = { default-features = false, path = "../../crates/pallet-chronicle"} + +[build-dependencies] +substrate-wasm-builder = { version = "13.0.0", optional = true } + +[features] +default = ["std"] +std = [ + "pallet-chronicle/std", + "frame-try-runtime?/std", + "frame-system-benchmarking?/std", + "frame-benchmarking?/std", + "parity-scale-codec/std", + "scale-info/std", + "frame-executive/std", + "frame-support/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "frame-try-runtime/std", + "pallet-aura/std", + "pallet-balances/std", + "pallet-grandpa/std", + "pallet-sudo/std", + "pallet-chronicle/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-consensus-grandpa/std", + "sp-core/std", + "sp-inherents/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-transaction-pool/std", + "sp-version/std", + "substrate-wasm-builder", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-grandpa/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", + "pallet-chronicle/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-try-runtime/try-runtime", + "frame-executive/try-runtime", + "frame-system/try-runtime", + "frame-support/try-runtime", + "pallet-aura/try-runtime", + "pallet-balances/try-runtime", + "pallet-grandpa/try-runtime", + "pallet-sudo/try-runtime", + "pallet-chronicle/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", +] diff --git a/node/runtime-chronicle/build.rs b/node/runtime-chronicle/build.rs new file mode 100644 index 000000000..c03d61853 --- /dev/null +++ b/node/runtime-chronicle/build.rs @@ -0,0 +1,10 @@ +fn main() { + #[cfg(feature = "std")] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build(); + } +} diff --git a/node/runtime-chronicle/src/lib.rs b/node/runtime-chronicle/src/lib.rs new file mode 100644 index 000000000..1d43cbba4 --- /dev/null +++ b/node/runtime-chronicle/src/lib.rs @@ -0,0 +1,573 @@ +#![cfg_attr(not(feature = "std"), no_std)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +use pallet_chronicle::operations; +use pallet_grandpa::AuthorityId as GrandpaId; +use sp_api::impl_runtime_apis; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::{ + AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, NumberFor, One, Verify, + }, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, MultiSignature, +}; +use sp_std::prelude::*; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; + +// A few exports that help ease life for downstream crates. +pub use frame_support::{ + construct_runtime, parameter_types, + traits::{ + ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, Randomness, + StorageInfo, + }, + weights::{ + constants::{ + BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, + }, + IdentityFee, Weight, + }, + StorageValue, +}; +pub use frame_system::Call as SystemCall; +pub use pallet_balances::Call as BalancesCall; +pub use pallet_timestamp::Call as TimestampCall; +use pallet_transaction_payment::{ConstFeeMultiplier, CurrencyAdapter, Multiplier}; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +pub use sp_runtime::{Perbill, Permill}; + +/// Import the template pallet. +pub use pallet_chronicle; + +/// An index to a block. +pub type BlockNumber = u32; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +/// Balance of an account. +pub type Balance = u128; + +/// Index of a transaction in the chain. +pub type Nonce = u32; + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know +/// the specifics of the runtime. They can then be made to be agnostic over specific formats +/// of data like extrinsics, allowing for them to continue syncing the network through upgrades +/// to even the core data structures. +pub mod opaque { + use super::*; + + pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; + + /// Opaque block header type. + pub type Header = generic::Header; + /// Opaque block type. + pub type Block = generic::Block; + /// Opaque block identifier type. + pub type BlockId = generic::BlockId; + + impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + pub grandpa: Grandpa, + } + } +} + +// To learn more about runtime versioning, see: +// https://docs.substrate.io/main-docs/build/upgrade#runtime-versioning +#[sp_version::runtime_version] +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("node-template"), + impl_name: create_runtime_str!("node-template"), + authoring_version: 1, + // The version of the runtime specification. A full node will not attempt to use its native + // runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`, + // `spec_version`, and `authoring_version` are the same between Wasm and native. + // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use + // the compatible custom types. + spec_version: 100, + impl_version: 1, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + state_version: 1, +}; + +/// This determines the average expected block time that we are targeting. +/// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. +/// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked +/// up by `pallet_aura` to implement `fn slot_duration()`. +/// +/// Change this to adjust the block time. +pub const MILLISECS_PER_BLOCK: u64 = 6000; + +// NOTE: Currently it is not possible to change the slot duration after the chain has started. +// Attempting to do so will brick block production. +pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + +// Time is measured by number of blocks. +pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); +pub const HOURS: BlockNumber = MINUTES * 60; +pub const DAYS: BlockNumber = HOURS * 24; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); + +parameter_types! { + pub const BlockHashCount: BlockNumber = 2400; + pub const Version: RuntimeVersion = VERSION; + /// We allow for 2 seconds of compute with a 6 second average block time. + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::with_sensible_defaults( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + NORMAL_DISPATCH_RATIO, + ); + pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength + ::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub const SS58Prefix: u8 = 42; +} + +// Configure FRAME pallets to include in runtime. + +impl frame_system::Config for Runtime { + /// The basic call filter to use in dispatchable. + type BaseCallFilter = frame_support::traits::Everything; + /// The block type for the runtime. + type Block = Block; + /// Block & extrinsics weights: base values and limits. + type BlockWeights = BlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = BlockLength; + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. + type RuntimeCall = RuntimeCall; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = AccountIdLookup; + /// The type for storing how many extrinsics an account has signed. + type Nonce = Nonce; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = BlakeTwo256; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + /// The ubiquitous origin type. + type RuntimeOrigin = RuntimeOrigin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// The weight of database operations that the runtime can invoke. + type DbWeight = RocksDbWeight; + /// Version of the runtime. + type Version = Version; + /// Converts a module to the index of the module in `construct_runtime!`. + /// + /// This type is being generated by `construct_runtime!`. + type PalletInfo = PalletInfo; + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = (); + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = SS58Prefix; + /// The set code logic, just the default since we're not a parachain. + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = ConstU32<32>; + type AllowMultipleBlocksPerSlot = ConstBool; +} + +impl pallet_grandpa::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + + type WeightInfo = (); + type MaxAuthorities = ConstU32<32>; + type MaxNominators = ConstU32<3>; + type MaxSetIdSessionEntries = ConstU64<0>; + + type KeyOwnerProof = sp_core::Void; + type EquivocationReportSystem = (); +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; + type WeightInfo = (); +} + +/// Existential deposit. +pub const EXISTENTIAL_DEPOSIT: u128 = 500; + +impl pallet_balances::Config for Runtime { + type MaxLocks = ConstU32<50>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU128; + type AccountStore = System; + type WeightInfo = pallet_balances::weights::SubstrateWeight; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); +} + +parameter_types! { + pub FeeMultiplier: Multiplier = Multiplier::one(); +} + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = CurrencyAdapter; + type OperationalFeeMultiplier = ConstU8<5>; + type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; + type FeeMultiplierUpdate = ConstFeeMultiplier; +} + +impl pallet_sudo::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WeightInfo = pallet_sudo::weights::SubstrateWeight; +} + +/// Configure the pallet-template in pallets/template. +impl pallet_chronicle::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_chronicle::weights::SubstrateWeight; + type OperationList = Vec; +} + +// Create the runtime by composing the FRAME pallets that were previously configured. +construct_runtime!( + pub struct Runtime { + System: frame_system, + Timestamp: pallet_timestamp, + Aura: pallet_aura, + Grandpa: pallet_grandpa, + Balances: pallet_balances, + TransactionPayment: pallet_transaction_payment, + Sudo: pallet_sudo, + // Include the custom logic from the pallet-template in the runtime. + Chronicle: pallet_chronicle, + } +); + +/// The address format for describing accounts. +pub type Address = sp_runtime::MultiAddress; +/// Block header type as expected by this runtime. +pub type Header = generic::Header; +/// Block type as expected by this runtime. +pub type Block = generic::Block; +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, +); + +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +/// The payload being signed in transactions. +pub type SignedPayload = generic::SignedPayload; +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, +>; + +#[cfg(feature = "runtime-benchmarks")] +#[macro_use] +extern crate frame_benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + define_benchmarks!( + [frame_benchmarking, BaselineBench::] + [frame_system, SystemBench::] + [pallet_balances, Balances] + [pallet_timestamp, Timestamp] + [pallet_sudo, Sudo] + [pallet_chronicle, Chronicle] + ); +} + +impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block); + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + } + + fn authorities() -> Vec { + Aura::authorities().into_inner() + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + opaque::SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + opaque::SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl sp_consensus_grandpa::GrandpaApi for Runtime { + fn grandpa_authorities() -> sp_consensus_grandpa::AuthorityList { + Grandpa::grandpa_authorities() + } + + fn current_set_id() -> sp_consensus_grandpa::SetId { + Grandpa::current_set_id() + } + + fn submit_report_equivocation_unsigned_extrinsic( + _equivocation_proof: sp_consensus_grandpa::EquivocationProof< + ::Hash, + NumberFor, + >, + _key_owner_proof: sp_consensus_grandpa::OpaqueKeyOwnershipProof, + ) -> Option<()> { + None + } + + fn generate_key_ownership_proof( + _set_id: sp_consensus_grandpa::SetId, + _authority_id: GrandpaId, + ) -> Option { + // NOTE: this is the only implementation possible since we've + // defined our key owner proof type as a bottom type (i.e. a type + // with no values). + None + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{baseline, Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + + let mut list = Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + + (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{baseline, Benchmarking, BenchmarkBatch, TrackedStorageKey}; + + use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + + impl frame_system_benchmarking::Config for Runtime {} + impl baseline::Config for Runtime {} + + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + Ok(batches) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. If any of the pre/post migration checks fail, we shall stop + // right here and right now. + let weight = Executive::try_runtime_upgrade(checks).unwrap(); + (weight, BlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + select: frame_try_runtime::TryStateSelect + ) -> Weight { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, signature_check, select).expect("execute-block failed") + } + } +} diff --git a/node/rust-toolchain.toml b/node/rust-toolchain.toml new file mode 100644 index 000000000..64daeff68 --- /dev/null +++ b/node/rust-toolchain.toml @@ -0,0 +1,14 @@ +[toolchain] +channel = "nightly" +components = [ + "cargo", + "clippy", + "rust-analyzer", + "rust-src", + "rust-std", + "rustc-dev", + "rustc", + "rustfmt", +] +targets = [ "wasm32-unknown-unknown" ] +profile = "minimal" diff --git a/node/rustfmt.toml b/node/rustfmt.toml new file mode 100644 index 000000000..441913f61 --- /dev/null +++ b/node/rustfmt.toml @@ -0,0 +1,23 @@ +# Basic +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" +# Imports +imports_granularity = "Crate" +reorder_imports = true +# Consistency +newline_style = "Unix" +# Format comments +comment_width = 100 +wrap_comments = true +# Misc +chain_width = 80 +spaces_around_ranges = false +binop_separator = "Back" +reorder_impl_items = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = false +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = false +use_field_init_shorthand = true diff --git a/node/scripts/init.sh b/node/scripts/init.sh new file mode 100755 index 000000000..f976f7235 --- /dev/null +++ b/node/scripts/init.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# This script is meant to be run on Unix/Linux based systems +set -e + +echo "*** Initializing WASM build environment" + +if [ -z $CI_PROJECT_NAME ] ; then + rustup update nightly + rustup update stable +fi + +rustup target add wasm32-unknown-unknown --toolchain nightly diff --git a/node/shell.nix b/node/shell.nix new file mode 100644 index 000000000..c08005c16 --- /dev/null +++ b/node/shell.nix @@ -0,0 +1,35 @@ +let + mozillaOverlay = + import (builtins.fetchGit { + url = "https://github.com/mozilla/nixpkgs-mozilla.git"; + rev = "57c8084c7ef41366993909c20491e359bbb90f54"; + }); + pinned = builtins.fetchGit { + # Descriptive name to make the store path easier to identify + url = "https://github.com/nixos/nixpkgs/"; + # Commit hash for nixos-unstable as of 2020-04-26 + # `git ls-remote https://github.com/nixos/nixpkgs nixos-unstable` + ref = "refs/heads/nixos-unstable"; + rev = "1fe6ed37fd9beb92afe90671c0c2a662a03463dd"; + }; + nixpkgs = import pinned { overlays = [ mozillaOverlay ]; }; + toolchain = with nixpkgs; (rustChannelOf { date = "2021-09-14"; channel = "nightly"; }); + rust-wasm = toolchain.rust.override { + targets = [ "wasm32-unknown-unknown" ]; + }; +in +with nixpkgs; pkgs.mkShell { + buildInputs = [ + clang + pkg-config + rust-wasm + ] ++ stdenv.lib.optionals stdenv.isDarwin [ + darwin.apple_sdk.frameworks.Security + ]; + + LIBCLANG_PATH = "${llvmPackages.libclang}/lib"; + PROTOC = "${protobuf}/bin/protoc"; + RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library/"; + ROCKSDB_LIB_DIR = "${rocksdb}/lib"; + +} diff --git a/rustfmt.toml b/rustfmt.toml index 797cc81fe..441913f61 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,23 @@ -unstable_features = true -imports_granularity="Crate" -reorder_impl_items = true +# Basic +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" +# Imports +imports_granularity = "Crate" reorder_imports = true -reorder_modules = true +# Consistency +newline_style = "Unix" +# Format comments +comment_width = 100 +wrap_comments = true +# Misc +chain_width = 80 +spaces_around_ranges = false +binop_separator = "Back" +reorder_impl_items = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = false +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = false +use_field_init_shorthand = true