feat: cli dev command (#153)

This commit is contained in:
Daniel Gushchyan 2024-08-28 16:45:09 -07:00 committed by GitHub
parent 74c3c47e4e
commit a9ea3fa9ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 2203 additions and 1371 deletions

3
.gitignore vendored
View file

@ -13,4 +13,7 @@ artifacts/
.DS_Store .DS_Store
.secrets/ .secrets/
**/.env.local **/.env.local
#cli
cli/quartz.toml cli/quartz.toml
.cache/

907
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,6 @@
resolver = "2" resolver = "2"
members = [ members = [
"apps/mtcs/enclave", "apps/mtcs/enclave",
"apps/transfers/enclave",
"cli", "cli",
"core/light-client-proofs/*", "core/light-client-proofs/*",
"core/quartz", "core/quartz",
@ -10,7 +9,7 @@ members = [
"cosmwasm/packages/*", "cosmwasm/packages/*",
"utils/*", "utils/*",
] ]
exclude = ["apps/mtcs/contracts/cw-tee-mtcs", "apps/transfers/contracts", "apps/mtcs/scripts"] exclude = ["apps/mtcs/contracts/cw-tee-mtcs", "apps/transfers/contracts", "apps/transfers/enclave"]
[workspace.package] [workspace.package]
version = "0.1.0" version = "0.1.0"
@ -54,7 +53,6 @@ tonic-build = { version = "=0.12.1", default-features = false, features = ["pros
tracing = { version = "0.1.39", default-features = false } tracing = { version = "0.1.39", default-features = false }
tracing-subscriber = { version = "0.3.17", default-features = false, features = ["fmt"] } tracing-subscriber = { version = "0.3.17", default-features = false, features = ["fmt"] }
uuid = { version = "1.4.1", default-features = false, features = ["serde"] } uuid = { version = "1.4.1", default-features = false, features = ["serde"] }
walkdir = { version = "2.5.0", default-features = false }
x509-cert = { version = "0.2.5", default-features = false } x509-cert = { version = "0.2.5", default-features = false }
x509-parser = { version = "0.16.0", features = ["default", "verify"] } x509-parser = { version = "0.16.0", features = ["default", "verify"] }
zeroize = { version = "1.7.0", default-features = false } zeroize = { version = "1.7.0", default-features = false }
@ -79,7 +77,6 @@ mc-attestation-verifier = { git = "https://github.com/informalsystems/attestatio
# quartz # quartz
cw-proof = { path = "core/light-client-proofs/cw-proof", default-features = false } cw-proof = { path = "core/light-client-proofs/cw-proof", default-features = false }
cycles-sync = { path = "utils/cycles-sync", default-features = false }
quartz-common = { path = "core/quartz-common"} quartz-common = { path = "core/quartz-common"}
quartz-cw = { path = "cosmwasm/packages/quartz-cw", default-features = false } quartz-cw = { path = "cosmwasm/packages/quartz-cw", default-features = false }
quartz-enclave = { path = "core/quartz", default-features = false } quartz-enclave = { path = "core/quartz", default-features = false }
@ -87,12 +84,12 @@ quartz-proto = { path = "core/quartz-proto", default-features = false }
quartz-tee-ra = { path = "cosmwasm/packages/quartz-tee-ra", default-features = false } quartz-tee-ra = { path = "cosmwasm/packages/quartz-tee-ra", default-features = false }
tm-prover = { path = "utils/tm-prover", default-features = false } tm-prover = { path = "utils/tm-prover", default-features = false }
tm-stateless-verifier = { path = "core/light-client-proofs/tm-stateless-verifier", default-features = false } tm-stateless-verifier = { path = "core/light-client-proofs/tm-stateless-verifier", default-features = false }
wasmd-client = { path = "cosmwasm/packages/wasmd-client", default-features = false }
# quartz apps # quartz apps
cw-tee-mtcs = { path = "apps/mtcs/contracts/cw-tee-mtcs", default-features = false } cw-tee-mtcs = { path = "apps/mtcs/contracts/cw-tee-mtcs", default-features = false }
mtcs = { git = "ssh://git@github.com/informalsystems/mtcs.git", default-features = false } mtcs = { git = "ssh://git@github.com/informalsystems/mtcs.git", default-features = false }
mtcs-enclave = { path = "apps/mtcs/enclave" } mtcs-enclave = { path = "apps/mtcs/enclave" }
transfers-contract = { path = "apps/transfers/contracts", default-features = false }
[profile.release] [profile.release]
opt-level = 3 opt-level = 3

View file

@ -0,0 +1,2 @@
[build]
target-dir = "target"

View file

@ -1,7 +1,7 @@
# Quartz manifest file # Quartz manifest file
loader.entrypoint = "file:{{ gramine.libos }}" loader.entrypoint = "file:{{ gramine.libos }}"
libos.entrypoint = "{{ quartz_dir }}/target/release/enclave" libos.entrypoint = "{{ quartz_dir }}/target/release/mtcs-enclave"
loader.log_level = "{{ log_level }}" loader.log_level = "{{ log_level }}"
@ -43,7 +43,7 @@ sgx.ra_client_linkable = {{ 'true' if ra_client_linkable == '1' else 'false' }}
sgx.trusted_files = [ sgx.trusted_files = [
"file:{{ gramine.libos }}", "file:{{ gramine.libos }}",
"file:{{ quartz_dir }}/target/release/enclave", "file:{{ quartz_dir }}/target/release/mtcs-enclave",
"file:{{ gramine.runtimedir() }}/", "file:{{ gramine.runtimedir() }}/",
"file:{{ arch_libdir }}/", "file:{{ arch_libdir }}/",
"file:/usr/{{ arch_libdir }}/", "file:/usr/{{ arch_libdir }}/",

View file

@ -42,7 +42,7 @@ pub struct SubmitObligationsMsg {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitObligationsMsgInner { pub struct SubmitObligationsMsgInner {
pub obligations: Vec<RawEncryptedObligation>, pub obligations: Vec<RawEncryptedObligation>,
pub liquidity_sources: Vec<Addr>, pub liquidity_sources: Vec<LiquiditySource>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]

View file

@ -1,8 +1,9 @@
mock_sgx = false mock_sgx = false
tx_sender = "admin" tx_sender = "admin"
chain_id = "testing" chain_id = "testing"
node_url = "127.0.0.1:26657" node_url = "127.0.0.1:25567"
enclave_rpc_addr = "http://127.0.0.1" enclave_rpc_addr = "http://127.0.0.1"
enclave_rpc_port = 11090 enclave_rpc_port = 11090
trusted_hash = "0E01C103E05108B2981C202C5AE41024444E0638A7D8C734916C2797EB1EE447" trusted_hash = ""
trusted_height = 8401 trusted_height = 0
release = true

View file

@ -0,0 +1,2 @@
[build]
target-dir = "target"

View file

@ -2,13 +2,12 @@
description = "An functioning example of a quartz app" description = "An functioning example of a quartz app"
ignore = [ ignore = [
"target/",
"contracts/Cargo.lock", "contracts/Cargo.lock",
"contracts/target", "contracts/target",
"contracts/target/*", "contracts/schema",
"enclave/Cargo.lock",
"enclave/target", "enclave/target",
"enclave/target/*", "enclave/src/prost/*",
"frontend/package-lock.json", "frontend/package-lock.json",
"frontend/public/images",
"trusted.hash",
"trusted.height"
] ]

View file

@ -149,9 +149,9 @@ dependencies = [
[[package]] [[package]]
name = "asn1-rs" name = "asn1-rs"
version = "0.6.1" version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048"
dependencies = [ dependencies = [
"asn1-rs-derive", "asn1-rs-derive",
"asn1-rs-impl", "asn1-rs-impl",
@ -165,13 +165,13 @@ dependencies = [
[[package]] [[package]]
name = "asn1-rs-derive" name = "asn1-rs-derive"
version = "0.5.0" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
"synstructure", "synstructure",
] ]
@ -183,7 +183,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -243,6 +243,12 @@ version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.7.1" version = "1.7.1"
@ -251,9 +257,12 @@ checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.6" version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
dependencies = [
"shlex",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -279,15 +288,15 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]] [[package]]
name = "cosmwasm-core" name = "cosmwasm-core"
version = "2.1.1" version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "367fc87c43759098a476ef90f915aadc66c300480ad9c155b512081fbf327bc1" checksum = "d905990ef3afb5753bb709dc7de88e9e370aa32bcc2f31731d4b533b63e82490"
[[package]] [[package]]
name = "cosmwasm-crypto" name = "cosmwasm-crypto"
version = "2.1.1" version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7c41f3e371ea457d3b98bb592c38858b46efcf614e0e988ec2ebbdb973954f" checksum = "5b2a7bd9c1dd9a377a4dc0f4ad97d24b03c33798cd5a6d7ceb8869b41c5d2f2d"
dependencies = [ dependencies = [
"ark-bls12-381", "ark-bls12-381",
"ark-ec", "ark-ec",
@ -308,20 +317,20 @@ dependencies = [
[[package]] [[package]]
name = "cosmwasm-derive" name = "cosmwasm-derive"
version = "2.1.1" version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c10510e8eb66cf7e109741b1e2c76ad18f30b5a1daa064f5f7115c1f733aaea0" checksum = "029910b409398fdf81955d7301b906caf81f2c42b013ea074fbd89720229c424"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
name = "cosmwasm-schema" name = "cosmwasm-schema"
version = "2.1.1" version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79879b6b7ef6a331b05030ce91ce46a7c4b0baf1ed6b382cce2e9a168109380" checksum = "4bc0d4d85e83438ab9a0fea9348446f7268bc016aacfebce37e998559f151294"
dependencies = [ dependencies = [
"cosmwasm-schema-derive", "cosmwasm-schema-derive",
"schemars", "schemars",
@ -332,20 +341,20 @@ dependencies = [
[[package]] [[package]]
name = "cosmwasm-schema-derive" name = "cosmwasm-schema-derive"
version = "2.1.1" version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b53e33c0e97170c7ac9cb440f4bc599a07f9cbb9b7e87916cca37b1239d57b" checksum = "edf5c8adac41bb7751c050d7c4c18675be19ee128714454454575e894424eeef"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
name = "cosmwasm-std" name = "cosmwasm-std"
version = "2.1.1" version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92011c39570876f340d5f9defa68bf92797b1c44421f1b9ea9b04a31d6defd33" checksum = "51dec99a2e478715c0a4277f0dbeadbb8466500eb7dec873d0924edd086e77f1"
dependencies = [ dependencies = [
"base64", "base64",
"bech32", "bech32",
@ -366,9 +375,9 @@ dependencies = [
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.12" version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -444,14 +453,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
name = "cw-multi-test" name = "cw-multi-test"
version = "2.1.0" version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0034bfb4c06dfc8b50f0b1a06c3fc0f2312a1bae568a97db65930de071288ba" checksum = "b0ae276e7a06ad1b7e7da78a3d68aba80634cde30ee7fe8259a94e653603fef8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bech32", "bech32",
@ -557,7 +566,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -568,7 +577,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -612,7 +621,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -637,22 +646,22 @@ dependencies = [
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "1.0.0-beta.6" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
dependencies = [ dependencies = [
"derive_more-impl", "derive_more-impl",
] ]
[[package]] [[package]]
name = "derive_more-impl" name = "derive_more-impl"
version = "1.0.0-beta.6" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
"unicode-xid", "unicode-xid",
] ]
@ -676,7 +685,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -886,9 +895,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.69" version = "0.3.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -913,9 +922,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.155" version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]] [[package]]
name = "log" name = "log"
@ -1116,9 +1125,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "primeorder" name = "primeorder"
@ -1140,9 +1152,9 @@ dependencies = [
[[package]] [[package]]
name = "prost" name = "prost"
version = "0.12.6" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc"
dependencies = [ dependencies = [
"bytes", "bytes",
"prost-derive", "prost-derive",
@ -1150,20 +1162,21 @@ dependencies = [
[[package]] [[package]]
name = "prost-derive" name = "prost-derive"
version = "0.12.6" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.10.5", "itertools 0.13.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
name = "quartz-common" name = "quartz-common"
version = "0.1.0" version = "0.1.0"
source = "git+ssh://git@github.com/informalsystems/cycles-quartz.git#302008c337b58fcad0373922a2787b9341eafd58"
dependencies = [ dependencies = [
"quartz-cw", "quartz-cw",
] ]
@ -1171,6 +1184,7 @@ dependencies = [
[[package]] [[package]]
name = "quartz-cw" name = "quartz-cw"
version = "0.1.0" version = "0.1.0"
source = "git+ssh://git@github.com/informalsystems/cycles-quartz.git#302008c337b58fcad0373922a2787b9341eafd58"
dependencies = [ dependencies = [
"cosmwasm-schema", "cosmwasm-schema",
"cosmwasm-std", "cosmwasm-std",
@ -1187,6 +1201,7 @@ dependencies = [
[[package]] [[package]]
name = "quartz-tee-ra" name = "quartz-tee-ra"
version = "0.1.0" version = "0.1.0"
source = "git+ssh://git@github.com/informalsystems/cycles-quartz.git#302008c337b58fcad0373922a2787b9341eafd58"
dependencies = [ dependencies = [
"cosmwasm-schema", "cosmwasm-schema",
"cosmwasm-std", "cosmwasm-std",
@ -1335,7 +1350,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1360,9 +1375,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.204" version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -1378,13 +1393,13 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.204" version = "1.0.208"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1395,14 +1410,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.122" version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -1435,7 +1450,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1449,6 +1464,12 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "signature" name = "signature"
version = "2.2.0" version = "2.2.0"
@ -1506,9 +1527,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.72" version = "2.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1523,7 +1544,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1543,7 +1564,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1610,9 +1631,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.4" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
@ -1634,34 +1655,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.92" version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell",
"wasm-bindgen-macro", "wasm-bindgen-macro",
] ]
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.92" version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.92" version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -1669,22 +1691,22 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.92" version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.92" version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
@ -1794,6 +1816,7 @@ version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [ dependencies = [
"byteorder",
"zerocopy-derive", "zerocopy-derive",
] ]
@ -1805,7 +1828,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]
[[package]] [[package]]
@ -1825,5 +1848,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.72", "syn 2.0.75",
] ]

View file

@ -44,7 +44,7 @@ cw20-base = { version = "2.0.0", default-features = false, features = ["library"
cw-utils = { version = "2.0.0", default-features = false } cw-utils = { version = "2.0.0", default-features = false }
# quartz # quartz
quartz-common = { path = "../../../core/quartz-common", default-features = false, features = ["contract"] } quartz-common = { git = "ssh://git@github.com/informalsystems/cycles-quartz.git", features=["contract"]}
# patch indirect deps # patch indirect deps
getrandom = { version = "0.2.15", default-features = false, features = ["js"] } getrandom = { version = "0.2.15", default-features = false, features = ["js"] }

View file

@ -15,33 +15,32 @@ default = []
[dependencies] [dependencies]
# external # external
anyhow.workspace = true anyhow = { version = "1.0.86", default-features = false }
clap.workspace = true clap = { version = "4.1.8", default-features = false, features = ["derive", "std"] }
color-eyre.workspace = true color-eyre = { version = "0.6.2", default-features = false }
ecies.workspace = true ecies = { version = "0.2.3", default-features = false, features = ["pure"] }
hex.workspace = true hex = { version = "0.4.3", default-features = false }
k256.workspace = true k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] }
prost.workspace = true prost = { version = "=0.13.1", default-features = false }
serde.workspace = true serde = { version = "1.0.203", default-features = false, features = ["derive"] }
serde_json.workspace = true serde_json = { version = "1.0.94", default-features = false, features = ["alloc"] }
sha2.workspace = true sha2 = { version = "0.10.8", default-features = false }
thiserror.workspace = true thiserror = { version = "1.0.49", default-features = false }
tokio.workspace = true tokio = { version = "=1.39.2", default-features = false, features = ["macros", "rt"] }
tonic.workspace = true tonic = { version = "=0.12.1", default-features = false, features = ["codegen", "prost", "transport"] }
# cosmos # cosmos
cosmrs.workspace = true cosmrs = { version = "=0.17.0", default-features = false }
cosmwasm-std.workspace = true cosmwasm-std = { version = "2.1.1", default-features = false, features = ["std"] }
cycles-sync.workspace = true tendermint = { version = "=0.38.1", default-features = false }
tendermint.workspace = true tendermint-light-client = { version = "=0.38.1", default-features = false, features = ["rust-crypto"] }
tendermint-light-client.workspace = true transfers-contract = { path = "../contracts", default-features = false }
transfers-contract.workspace = true
# quartz # quartz
quartz-common = { workspace=true, features=["full"]} quartz-common = { git = "ssh://git@github.com/informalsystems/cycles-quartz.git", features=["full"]}
[dev-dependencies] [dev-dependencies]
cw-multi-test = "2.1.0" cw-multi-test = "2.1.0"
[build-dependencies] [build-dependencies]
tonic-build.workspace = true tonic-build = { version = "=0.12.1", default-features = false, features = ["prost", "transport"] }

View file

@ -1,7 +1,7 @@
# Quartz-based app manifest file # Quartz-based app manifest file
loader.entrypoint = "file:{{ gramine.libos }}" loader.entrypoint = "file:{{ gramine.libos }}"
libos.entrypoint = "{{ enclave_executable }}" libos.entrypoint = "{{ quartz_dir }}/target/release/quartz-app-transfers-enclave"
loader.log_level = "{{ log_level }}" loader.log_level = "{{ log_level }}"
@ -30,7 +30,7 @@ fs.mounts = [
{ uri = "file:{{ gramine.runtimedir() }}", path = "/lib" }, { uri = "file:{{ gramine.runtimedir() }}", path = "/lib" },
{ uri = "file:{{ arch_libdir }}", path = "{{ arch_libdir }}" }, { uri = "file:{{ arch_libdir }}", path = "{{ arch_libdir }}" },
{ uri = "file:/usr/{{ arch_libdir }}", path = "/usr{{ arch_libdir }}" }, { uri = "file:/usr/{{ arch_libdir }}", path = "/usr{{ arch_libdir }}" },
{ uri = "file:{{ enclave_dir }}", path = "{{ enclave_dir }}" }, { uri = "file:{{ quartz_dir }}", path = "{{ quartz_dir }}" },
] ]
# sgx.debug = true # sgx.debug = true
@ -44,15 +44,15 @@ sgx.ra_client_linkable = {{ 'true' if ra_client_linkable == '1' else 'false' }}
sgx.trusted_files = [ sgx.trusted_files = [
"file:{{ gramine.libos }}", "file:{{ gramine.libos }}",
"file:{{ enclave_executable }}", "file:{{ quartz_dir }}/target/release/",
"file:{{ gramine.runtimedir() }}/", "file:{{ gramine.runtimedir() }}/",
"file:{{ arch_libdir }}/", "file:{{ arch_libdir }}/",
"file:/usr/{{ arch_libdir }}/", "file:/usr/{{ arch_libdir }}/",
] ]
sgx.allowed_files = [ sgx.allowed_files = [
"file:{{ enclave_dir }}/exchange.sk", "file:{{ quartz_dir }}/exchange.sk",
"file:{{ enclave_dir }}/request.json", "file:{{ quartz_dir }}/request.json",
] ]
sys.insecure__allow_eventfd = true sys.insecure__allow_eventfd = true

View file

@ -84,6 +84,12 @@ impl HasUserData for QueryResponseMessage {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StatusResponseMessage {
address: Addr,
encrypted_bal: HexBinary,
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
struct AttestedMsg<M> { struct AttestedMsg<M> {
msg: M, msg: M,

View file

@ -1,8 +1,9 @@
mock_sgx = false mock_sgx = false
tx_sender = "admin" tx_sender = "admin"
chain_id = "testing" chain_id = "testing"
node_url = "127.0.0.1:26657" node_url = "127.0.0.1:25567"
enclave_rpc_addr = "http://127.0.0.1" enclave_rpc_addr = "http://127.0.0.1"
enclave_rpc_port = 11090 enclave_rpc_port = 11090
trusted_hash = "0E01C103E05108B2981C202C5AE41024444E0638A7D8C734916C2797EB1EE447" trusted_hash = ""
trusted_height = 8401 trusted_height = 0
release = true

View file

@ -27,14 +27,23 @@ prost.workspace = true
tokio = { workspace = true, features = ["process"] } tokio = { workspace = true, features = ["process"] }
tonic.workspace = true tonic.workspace = true
once_cell = "1.19.0" once_cell = "1.19.0"
reqwest = { version = "0.12.2", default-features = false, features = ["json", "rustls-tls"] } reqwest = { workspace = true, default-features = false, features = ["json", "rustls-tls"] }
anyhow = "1.0.86" anyhow.workspace = true
base64 = "0.22.1" base64 = "0.22.1"
subtle-encoding = "0.5.1" subtle-encoding.workspace = true
futures-util = "0.3.30" futures-util = "0.3.30"
target-lexicon = "0.12.16" target-lexicon = "0.12.16"
regex = "1.10.5" regex = "1.10.5"
watchexec = "4.1.0"
watchexec-events = "3.0.0"
watchexec-signals = "3.0.0"
miette = "7.2.0"
ctrlc = { version = "3.4.5", features=["termination"]}
xxhash-rust = { version = "0.8.12", features=["xxh3"] }
toml = "0.8.19" toml = "0.8.19"
figment = { version = "0.10.19", features=["env", "toml"] }
clearscreen = "3.0.0"
cargo_metadata = "0.18.1"
# cosmos # cosmos
cosmrs.workspace = true cosmrs.workspace = true
@ -43,9 +52,9 @@ tendermint.workspace = true
tendermint-light-client.workspace = true tendermint-light-client.workspace = true
tendermint-rpc = { workspace = true, features=["websocket-client", "http-client"]} tendermint-rpc = { workspace = true, features=["websocket-client", "http-client"]}
cycles-sync = { workspace = true}
tm-prover = { workspace = true} tm-prover = { workspace = true}
quartz-common = { workspace = true, features=["contract"]} quartz-common = { workspace = true, features=["contract"]}
quartz-tee-ra = { workspace = true} quartz-tee-ra = { workspace = true}
mtcs-enclave = { workspace = true, optional = false} mtcs-enclave = { workspace = true, optional = false}
figment = { version = "0.10.19", features=["env", "toml"] } wasmd-client.workspace = true
tempfile.workspace = true

151
cli/src/cache.rs Normal file
View file

@ -0,0 +1,151 @@
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use tokio::{
fs::File,
io::{AsyncReadExt, BufReader},
};
use tracing::debug;
use xxhash_rust::xxh3::Xxh3;
use crate::{config::Config, error::Error};
const BUFFER_SIZE: usize = 16384; // 16 KB buffer
type Hash = u64;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
struct DeployedContract {
code_id: u64,
contract_hash: Hash,
}
// Porcelain
impl Config {
pub async fn contract_has_changed(&self, file: &Path) -> Result<bool, Error> {
let cur_hash: Hash = Self::gen_hash(file).await?;
debug!("current file hash: {}", cur_hash);
let cached_file_path = Self::to_cache_path(self, file)?;
if !cached_file_path.exists() {
return Ok(true);
}
let cached_contract = Self::read_from_cache(cached_file_path.as_path()).await?;
debug!("cached file hash: {}", cached_contract.contract_hash);
Ok(cur_hash != cached_contract.contract_hash)
}
/// Return a hash of the given file's contents
pub async fn gen_hash(file: &Path) -> Result<Hash, Error> {
let file = File::open(file)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
let mut reader = BufReader::new(file);
let mut hasher = Xxh3::new();
let mut buffer = [0; BUFFER_SIZE];
loop {
let bytes_read = reader
.read(&mut buffer)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
// Finalize the hash
let hash = hasher.digest();
Ok(hash)
}
pub async fn save_codeid_to_cache(&self, file: &Path, code_id: u64) -> Result<(), Error> {
let contract_hash = Self::gen_hash(file).await?;
let dest = Self::to_cache_path(self, file)?;
let deployed_contract = DeployedContract {
code_id,
contract_hash,
};
Self::write_to_cache(dest.as_path(), &deployed_contract).await
}
pub async fn get_cached_codeid(&self, file: &Path) -> Result<u64, Error> {
let cache_path = Self::to_cache_path(self, file)?;
let code_id = Self::read_from_cache(cache_path.as_path()).await?.code_id;
Ok(code_id)
}
// Plumbing
fn to_cache_path(&self, file: &Path) -> Result<PathBuf, Error> {
// Get cache filepath (".quartz/cache/example.wasm.json") from "example.wasm" filepath
let mut filename = file
.file_name()
.ok_or(Error::PathNotFile(file.display().to_string()))?
.to_os_string();
filename.push(".json");
let cached_file_path = Self::cache_dir(self)?.join::<PathBuf>(filename.into());
Ok(cached_file_path)
}
/// Retreive hash from cache file
async fn read_from_cache(cache_file: &Path) -> Result<DeployedContract, Error> {
let content = tokio::fs::read_to_string(cache_file)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
serde_json::from_str(&content).map_err(|e| Error::GenericErr(e.to_string()))
}
/// Write a given file's contents hash to a file in cache directory
async fn write_to_cache(cache_file: &Path, data: &DeployedContract) -> Result<(), Error> {
let content = serde_json::to_string(data).map_err(|e| Error::GenericErr(e.to_string()))?;
tokio::fs::write(cache_file, content)
.await
.map_err(|e| Error::GenericErr(e.to_string()))
}
pub fn cache_dir(&self) -> Result<PathBuf, Error> {
Ok(self.app_dir.join(".cache/"))
}
pub fn build_log_dir(&self) -> Result<PathBuf, Error> {
Ok(self.app_dir.join(".cache/log/"))
}
/// Creates the build log if it isn't created already, returns relative path from app_dir to log directory
pub async fn create_build_log(&self) -> Result<PathBuf, Error> {
let log_dir = Self::build_log_dir(self)?;
if !log_dir.exists() {
tokio::fs::create_dir_all(&log_dir)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
}
Ok(log_dir)
}
pub async fn log_build(&self, is_enclave: bool) -> Result<(), Error> {
let log_dir = Self::create_build_log(self).await?;
let filename = match is_enclave {
true => "enclave",
false => "contract",
};
tokio::fs::write(log_dir.join(filename), "test")
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
Ok(())
}
}

View file

@ -35,8 +35,8 @@ pub struct Cli {
/// Enable mock SGX mode for testing purposes. /// Enable mock SGX mode for testing purposes.
/// This flag disables the use of an Intel SGX processor and allows the system to run without remote attestations. /// This flag disables the use of an Intel SGX processor and allows the system to run without remote attestations.
#[arg(long)] #[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "is_false")]
pub mock_sgx: Option<bool>, pub mock_sgx: bool,
/// Path to Quartz app directory /// Path to Quartz app directory
/// Defaults to current working dir /// Defaults to current working dir
@ -50,6 +50,10 @@ pub struct Cli {
pub command: Command, pub command: Command,
} }
fn is_false(b: &bool) -> bool {
!(*b)
}
#[derive(Debug, Subcommand, Serialize, Clone)] #[derive(Debug, Subcommand, Serialize, Clone)]
pub enum Command { pub enum Command {
/// Create an empty Quartz app from a template /// Create an empty Quartz app from a template
@ -69,6 +73,8 @@ pub enum Command {
#[command(subcommand)] #[command(subcommand)]
enclave_command: EnclaveCommand, enclave_command: EnclaveCommand,
}, },
/// Build, deploy, perform handshake, and run quartz app while listening for changes
Dev(DevArgs),
} }
#[derive(Debug, Clone, Subcommand, Serialize)] #[derive(Debug, Clone, Subcommand, Serialize)]
@ -98,6 +104,10 @@ pub struct HandshakeArgs {
#[arg(short, long, value_parser = wasmaddr_to_id)] #[arg(short, long, value_parser = wasmaddr_to_id)]
pub contract: AccountId, pub contract: AccountId,
/// Fetch latest trusted hash and height from the chain instead of existing configuration
#[arg(long)]
pub use_latest_trusted: bool,
/// Name or address of private key with which to sign /// Name or address of private key with which to sign
#[arg(long)] #[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@ -126,8 +136,9 @@ pub struct HandshakeArgs {
#[derive(Debug, Parser, Clone, Serialize, Deserialize)] #[derive(Debug, Parser, Clone, Serialize, Deserialize)]
pub struct ContractBuildArgs { pub struct ContractBuildArgs {
/// Path to Cargo manifest file for CosmWasm contract package
#[arg(long)] #[arg(long)]
pub manifest_path: PathBuf, pub contract_manifest: PathBuf,
} }
#[derive(Debug, Parser, Clone, Serialize, Deserialize)] #[derive(Debug, Parser, Clone, Serialize, Deserialize)]
@ -155,16 +166,17 @@ pub struct ContractDeployArgs {
#[arg(long, default_value = "Quartz App Contract")] #[arg(long, default_value = "Quartz App Contract")]
pub label: String, pub label: String,
/// Path to contract wasm binary for deployment /// Path to Cargo manifest file for CosmWasm contract package
#[arg(long)] #[arg(long)]
pub wasm_bin_path: PathBuf, pub contract_manifest: PathBuf,
} }
#[derive(Debug, Parser, Clone, Serialize, Deserialize)] #[derive(Debug, Parser, Clone, Serialize, Deserialize)]
pub struct EnclaveBuildArgs { pub struct EnclaveBuildArgs {
/// Path to Cargo.toml file of the Quartz app's enclave package, defaults to './enclave/Cargo.toml' if unspecified /// Whether to target release or dev
#[arg(long, default_value = "./enclave/Cargo.toml")] #[arg(long)]
pub manifest_path: PathBuf, #[serde(skip_serializing_if = "is_false")]
pub release: bool,
} }
#[derive(Debug, Parser, Clone, Serialize, Deserialize)] #[derive(Debug, Parser, Clone, Serialize, Deserialize)]
@ -177,6 +189,28 @@ pub struct EnclaveStartArgs {
/// Fetch latest trusted hash and height from the chain instead of existing configuration /// Fetch latest trusted hash and height from the chain instead of existing configuration
#[arg(long)] #[arg(long)]
pub use_latest_trusted: bool, pub use_latest_trusted: bool,
/// Whether to target release or dev
#[arg(long)]
#[serde(skip_serializing_if = "is_false")]
pub release: bool,
}
#[derive(Debug, Parser, Clone, Serialize, Deserialize)]
pub struct DevArgs {
/// Automatically deploy and instantiate new cosmwasm contract instance upon changes to source
#[arg(long)]
pub watch: bool,
/// Fetch latest trusted hash and height from the chain instead of existing configuration
#[arg(long)]
pub use_latest_trusted: bool,
#[command(flatten)]
pub contract_deploy: ContractDeployArgs,
#[command(flatten)]
pub enclave_build: EnclaveBuildArgs,
} }
pub trait ToFigment { pub trait ToFigment {
@ -196,6 +230,9 @@ impl ToFigment for Command {
EnclaveCommand::Build(args) => Figment::from(Serialized::defaults(args)), EnclaveCommand::Build(args) => Figment::from(Serialized::defaults(args)),
EnclaveCommand::Start(args) => Figment::from(Serialized::defaults(args)), EnclaveCommand::Start(args) => Figment::from(Serialized::defaults(args)),
}, },
Command::Dev(args) => Figment::from(Serialized::defaults(args))
.merge(Serialized::defaults(&args.contract_deploy))
.merge(Serialized::defaults(&args.enclave_build)),
} }
} }
} }

View file

@ -32,7 +32,7 @@ pub struct Config {
/// Path to Quartz app directory /// Path to Quartz app directory
/// Defaults to current working dir /// Defaults to current working dir
#[serde(default = "default_app_dir")] #[serde(default = "default_app_dir", skip_serializing)]
pub app_dir: PathBuf, pub app_dir: PathBuf,
/// Trusted height for light client proofs /// Trusted height for light client proofs
@ -42,6 +42,10 @@ pub struct Config {
/// Trusted hash for block at trusted_height for light client proofs /// Trusted hash for block at trusted_height for light client proofs
#[serde(default)] #[serde(default)]
pub trusted_hash: String, pub trusted_hash: String,
/// Whether to build for release or debug
#[serde(default)]
pub release: bool,
} }
fn default_rpc_addr() -> String { fn default_rpc_addr() -> String {
@ -80,6 +84,7 @@ impl Default for Config {
app_dir: default_app_dir(), app_dir: default_app_dir(),
trusted_height: u64::default(), trusted_height: u64::default(),
trusted_hash: String::default(), trusted_hash: String::default(),
release: false,
} }
} }
} }
@ -89,3 +94,9 @@ impl AsRef<Config> for Config {
self self
} }
} }
impl Config {
pub fn enclave_rpc(&self) -> String {
format!("{}:{}", self.enclave_rpc_addr, self.enclave_rpc_port)
}
}

View file

@ -15,6 +15,8 @@ pub enum Error {
TomlError(String), TomlError(String),
/// Tendermint error: {0} /// Tendermint error: {0}
TendermintError(String), TendermintError(String),
/// Clearscreen error: {0}
ClearscreenError(String),
} }
impl From<std::io::Error> for Error { impl From<std::io::Error> for Error {
@ -40,3 +42,9 @@ impl From<tendermint::Error> for Error {
Error::TendermintError(err.to_string()) Error::TendermintError(err.to_string())
} }
} }
impl From<clearscreen::Error> for Error {
fn from(err: clearscreen::Error) -> Self {
Error::ClearscreenError(err.to_string())
}
}

View file

@ -6,6 +6,7 @@ pub mod utils;
// commands // commands
pub mod contract_build; pub mod contract_build;
pub mod contract_deploy; pub mod contract_deploy;
pub mod dev;
pub mod enclave_build; pub mod enclave_build;
pub mod enclave_start; pub mod enclave_start;
pub mod handshake; pub mod handshake;
@ -38,6 +39,7 @@ impl Handler for Request {
Request::ContractDeploy(request) => request.handle(config).await, Request::ContractDeploy(request) => request.handle(config).await,
Request::EnclaveBuild(request) => request.handle(config).await, Request::EnclaveBuild(request) => request.handle(config).await,
Request::EnclaveStart(request) => request.handle(config).await, Request::EnclaveStart(request) => request.handle(config).await,
Request::Dev(request) => request.handle(config).await,
} }
.map(Into::into) .map(Into::into)
} }

View file

@ -1,7 +1,8 @@
use std::process::Command; use std::process::Command;
use async_trait::async_trait; use async_trait::async_trait;
use tracing::{debug, trace}; use color_eyre::owo_colors::OwoColorize;
use tracing::{debug, info};
use crate::{ use crate::{
config::Config, config::Config,
@ -21,11 +22,22 @@ impl Handler for ContractBuildRequest {
config: C, config: C,
) -> Result<Self::Response, Self::Error> { ) -> Result<Self::Response, Self::Error> {
let config = config.as_ref(); let config = config.as_ref();
info!("{}", "\nPeforming Contract Build".blue().bold());
let mut cargo = Command::new("cargo"); let mut cargo = Command::new("cargo");
let command = cargo let command = cargo
.arg("wasm") .arg("build")
.args(["--manifest-path", &self.manifest_path.display().to_string()]) .arg("--release")
.args(["--target", "wasm32-unknown-unknown"])
.arg("--lib")
.args([
"--target-dir",
&config.app_dir.join("target").display().to_string(),
])
.args([
"--manifest-path",
&self.contract_manifest.display().to_string(),
])
.env("RUSTFLAGS", "-C link-arg=-s"); .env("RUSTFLAGS", "-C link-arg=-s");
if config.mock_sgx { if config.mock_sgx {
@ -33,7 +45,7 @@ impl Handler for ContractBuildRequest {
command.arg("--features=mock-sgx"); command.arg("--features=mock-sgx");
} }
trace!("🚧 Building contract binary ..."); info!("{}", "🚧 Building contract binary ...".green().bold());
let status = command let status = command
.status() .status()
.map_err(|e| Error::GenericErr(e.to_string()))?; .map_err(|e| Error::GenericErr(e.to_string()))?;
@ -45,6 +57,8 @@ impl Handler for ContractBuildRequest {
))); )));
} }
config.log_build(false).await?;
Ok(ContractBuildResponse.into()) Ok(ContractBuildResponse.into())
} }
} }

View file

@ -1,7 +1,8 @@
use std::env::current_dir; use std::path::Path;
use async_trait::async_trait; use async_trait::async_trait;
use cycles_sync::wasmd_client::{CliWasmdClient, WasmdClient}; use cargo_metadata::MetadataCommand;
use color_eyre::owo_colors::OwoColorize;
use quartz_common::contract::{ use quartz_common::contract::{
msg::execute::attested::{RawEpidAttestation, RawMockAttestation}, msg::execute::attested::{RawEpidAttestation, RawMockAttestation},
prelude::QuartzInstantiateMsg, prelude::QuartzInstantiateMsg,
@ -10,16 +11,20 @@ use reqwest::Url;
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use serde_json::json; use serde_json::json;
use tendermint_rpc::HttpClient; use tendermint_rpc::HttpClient;
use tracing::{debug, info, trace}; use tracing::{debug, info};
use wasmd_client::{CliWasmdClient, WasmdClient};
use super::utils::{ use super::utils::{
helpers::{block_tx_commit, run_relay}, helpers::block_tx_commit,
types::{Log, WasmdTxResponse}, types::{Log, WasmdTxResponse},
}; };
use crate::{ use crate::{
config::Config, config::Config,
error::Error, error::Error,
handler::{utils::types::RelayMessage, Handler}, handler::{
utils::{helpers::run_relay_rust, types::RelayMessage},
Handler,
},
request::contract_deploy::ContractDeployRequest, request::contract_deploy::ContractDeployRequest,
response::{contract_deploy::ContractDeployResponse, Response}, response::{contract_deploy::ContractDeployResponse, Response},
}; };
@ -34,15 +39,32 @@ impl Handler for ContractDeployRequest {
config: C, config: C,
) -> Result<Self::Response, Self::Error> { ) -> Result<Self::Response, Self::Error> {
let config = config.as_ref(); let config = config.as_ref();
info!("{}", "\nPeforming Contract Deploy".blue().bold());
trace!("initializing directory structure..."); // Get contract package name in snake_case
let package_name = MetadataCommand::new()
.manifest_path(&self.contract_manifest)
.exec()
.map_err(|e| Error::GenericErr(e.to_string()))?
.root_package()
.ok_or("No root package found in the metadata")
.map_err(|e| Error::GenericErr(e.to_string()))?
.name
.clone()
.replace('-', "_");
let wasm_bin_path = config
.app_dir
.join("target/wasm32-unknown-unknown/release")
.join(package_name)
.with_extension("wasm");
let (code_id, contract_addr) = if config.mock_sgx { let (code_id, contract_addr) = if config.mock_sgx {
deploy::<RawMockAttestation>(self, config) deploy::<RawMockAttestation>(wasm_bin_path.as_path(), self, config)
.await .await
.map_err(|e| Error::GenericErr(e.to_string()))? .map_err(|e| Error::GenericErr(e.to_string()))?
} else { } else {
deploy::<RawEpidAttestation>(self, config) deploy::<RawEpidAttestation>(wasm_bin_path.as_path(), self, config)
.await .await
.map_err(|e| Error::GenericErr(e.to_string()))? .map_err(|e| Error::GenericErr(e.to_string()))?
}; };
@ -56,40 +78,41 @@ impl Handler for ContractDeployRequest {
} }
async fn deploy<DA: Serialize + DeserializeOwned>( async fn deploy<DA: Serialize + DeserializeOwned>(
wasm_bin_path: &Path,
args: ContractDeployRequest, args: ContractDeployRequest,
config: &Config, config: &Config,
) -> Result<(u64, String), anyhow::Error> { ) -> Result<(u64, String), anyhow::Error> {
// TODO: Replace with call to Rust package
let relay_path = current_dir()?.join("../");
let httpurl = Url::parse(&format!("http://{}", config.node_url))?; let httpurl = Url::parse(&format!("http://{}", config.node_url))?;
let tmrpc_client = HttpClient::new(httpurl.as_str())?; let tmrpc_client = HttpClient::new(httpurl.as_str())?;
let wasmd_client = CliWasmdClient::new(Url::parse(httpurl.as_str())?); let wasmd_client = CliWasmdClient::new(Url::parse(httpurl.as_str())?);
info!("\n🚀 Deploying {} Contract\n", args.label); info!("🚀 Deploying {} Contract", args.label);
let contract_path = args.wasm_bin_path; let code_id = if config.contract_has_changed(wasm_bin_path).await? {
// .join("contracts/cw-tee-mtcs/target/wasm32-unknown-unknown/release/cw_tee_mtcs.wasm");
// TODO: uncertain about the path -> string conversion
let deploy_output: WasmdTxResponse = serde_json::from_str(&wasmd_client.deploy( let deploy_output: WasmdTxResponse = serde_json::from_str(&wasmd_client.deploy(
&config.chain_id, &config.chain_id,
&config.tx_sender, &config.tx_sender,
contract_path.display().to_string(), wasm_bin_path.display().to_string(),
)?)?; )?)?;
let res = block_tx_commit(&tmrpc_client, deploy_output.txhash).await?; let res = block_tx_commit(&tmrpc_client, deploy_output.txhash).await?;
let log: Vec<Log> = serde_json::from_str(&res.tx_result.log)?; let log: Vec<Log> = serde_json::from_str(&res.tx_result.log)?;
let code_id: u64 = log[0].events[1].attributes[1].value.parse()?; let code_id: u64 = log[0].events[1].attributes[1].value.parse()?;
config.save_codeid_to_cache(wasm_bin_path, code_id).await?;
info!("\n🚀 Communicating with Relay to Instantiate...\n"); code_id
let raw_init_msg = run_relay::<QuartzInstantiateMsg<DA>>( } else {
relay_path.as_path(), config.get_cached_codeid(wasm_bin_path).await?
};
info!("🚀 Communicating with Relay to Instantiate...");
let raw_init_msg = run_relay_rust::<QuartzInstantiateMsg<DA>>(
config.enclave_rpc(),
config.mock_sgx, config.mock_sgx,
RelayMessage::Instantiate, RelayMessage::Instantiate,
) )
.await?; .await?;
info!("\n🚀 Instantiating {} Contract\n", args.label); info!("🚀 Instantiating {}", args.label);
let mut init_msg = args.init_msg; let mut init_msg = args.init_msg;
init_msg["quartz"] = json!(raw_init_msg); init_msg["quartz"] = json!(raw_init_msg);
@ -105,9 +128,9 @@ async fn deploy<DA: Serialize + DeserializeOwned>(
let log: Vec<Log> = serde_json::from_str(&res.tx_result.log)?; let log: Vec<Log> = serde_json::from_str(&res.tx_result.log)?;
let contract_addr: &String = &log[0].events[1].attributes[0].value; let contract_addr: &String = &log[0].events[1].attributes[0].value;
info!("\n🚀 Successfully deployed and instantiated contract!"); info!("🚀 Successfully deployed and instantiated contract!");
info!("\n🆔 Code ID: {}", code_id); info!("🆔 Code ID: {}", code_id);
info!("\n📌 Contract Address: {}", contract_addr); info!("📌 Contract Address: {}", contract_addr);
debug!("{contract_addr}"); debug!("{contract_addr}");

348
cli/src/handler/dev.rs Normal file
View file

@ -0,0 +1,348 @@
use std::{path::PathBuf, process::exit, time::Duration};
use async_trait::async_trait;
use color_eyre::owo_colors::OwoColorize;
// todo get rid of this?
use miette::{IntoDiagnostic, Result};
use quartz_common::proto::core_client::CoreClient;
use tokio::{
sync::{mpsc, watch},
time::sleep,
};
use tracing::{debug, info};
use watchexec::Watchexec;
use watchexec_signals::Signal;
use crate::{
error::Error,
handler::{utils::helpers::wasmaddr_to_id, Handler},
request::{
contract_build::ContractBuildRequest, contract_deploy::ContractDeployRequest,
dev::DevRequest, enclave_build::EnclaveBuildRequest, enclave_start::EnclaveStartRequest,
handshake::HandshakeRequest,
},
response::{dev::DevResponse, Response},
Config, BANNER,
};
#[async_trait]
impl Handler for DevRequest {
type Error = Error;
type Response = Response;
async fn handle<C: AsRef<Config> + Send>(
self,
config: C,
) -> Result<Self::Response, Self::Error> {
let config = config.as_ref();
info!("\nPeforming Dev");
let (tx, rx) = mpsc::channel::<DevRebuild>(32);
let _res = tx.send(DevRebuild::Init).await;
if self.watch {
tokio::spawn(watcher(tx, config.build_log_dir()?));
}
dev_driver(rx, &self, config.clone()).await?;
Ok(DevResponse.into())
}
}
#[derive(Debug, Clone)]
enum DevRebuild {
Init,
Enclave,
Contract,
}
async fn dev_driver(
mut rx: mpsc::Receiver<DevRebuild>,
args: &DevRequest,
config: Config,
) -> Result<(), Error> {
// State
let mut shutdown_tx: Option<watch::Sender<()>> = None;
let mut first_enclave_message = true;
let mut first_contract_message = true;
let mut contract = String::from("");
// Shutdown enclave upon interruption
let shutdown_tx_cpy = shutdown_tx.clone();
ctrlc::set_handler(move || {
if let Some(tx) = &shutdown_tx_cpy {
let _res = tx.send(());
}
exit(130)
})
.expect("Error setting Ctrl-C handler");
// Drive
while let Some(dev) = rx.recv().await {
match dev {
DevRebuild::Init => {
clearscreen::clear()?;
println!("{}", BANNER.yellow().bold());
info!("{}", "Launching quartz app...".green().bold());
// Build enclave
let enclave_build = EnclaveBuildRequest {
release: args.release,
};
enclave_build.handle(&config).await?;
// Build contract
let contract_build = ContractBuildRequest {
contract_manifest: args.contract_manifest.clone(),
};
contract_build.handle(&config).await?;
// Start enclave in background
let new_shutdown_tx = spawn_enclave_start(args, &config).await?;
// Deploy new contract and perform handshake
let res = deploy_and_handshake(None, args, &config).await;
// Save resulting contract address or shutdown and return error
match res {
Ok(res_contract) => {
// Set state
contract = res_contract;
shutdown_tx = Some(new_shutdown_tx);
info!("{}", "Enclave is listening for requests...".green().bold());
}
Err(e) => {
eprintln!("Error launching quartz app");
new_shutdown_tx
.send(())
.expect("Could not send signal on channel");
return Err(e);
}
}
}
DevRebuild::Enclave => {
if first_enclave_message {
first_enclave_message = false;
continue;
}
clearscreen::clear()?;
println!("{}", BANNER.yellow().bold());
info!("{}", "Rebuilding Enclave...".green().bold());
if let Some(shutdown_tx) = shutdown_tx.clone() {
let _res = shutdown_tx.send(());
}
info!("Waiting 1 second for the enclave to shut down");
sleep(Duration::from_secs(1)).await;
let new_shutdown_tx = spawn_enclave_start(args, &config).await?;
// todo: should not unconditionally deploy here
let res = deploy_and_handshake(Some(&contract), args, &config).await;
match res {
Ok(res_contract) => {
// Set state
contract = res_contract;
shutdown_tx = Some(new_shutdown_tx);
info!("{}", "Enclave is listening for requests...".green().bold());
}
Err(e) => {
eprintln!("Error restarting enclave and handshake");
new_shutdown_tx
.send(())
.expect("Could not send signal on channel");
return Err(e);
}
}
}
DevRebuild::Contract => {
if first_contract_message {
first_contract_message = false;
continue;
}
clearscreen::clear()?;
println!("{}", BANNER.yellow().bold());
info!("{}", "Rebuilding Contract...".green().bold());
if let Some(shutdown_tx) = shutdown_tx.clone() {
let res = deploy_and_handshake(None, args, &config).await;
match res {
Ok(res_contract) => contract = res_contract,
Err(e) => {
eprintln!("Error deploying contract and handshake:");
shutdown_tx
.send(())
.expect("Could not send signal on channel");
return Err(e);
}
}
} else {
return Err(Error::GenericErr(
"Attempting to redeploy contract, but enclave isn't running".to_string(),
));
}
info!("{}", "Enclave is listening for requests...".green().bold());
}
}
}
Ok(())
}
// Spawns enclve start in a separate task which runs in the background
async fn spawn_enclave_start(
args: &DevRequest,
config: &Config,
) -> Result<watch::Sender<()>, Error> {
// In separate process, launch the enclave
let (shutdown_tx, shutdown_rx) = watch::channel(());
let enclave_start = EnclaveStartRequest {
shutdown_rx: Some(shutdown_rx),
use_latest_trusted: args.use_latest_trusted,
};
let config_cpy = config.clone();
tokio::spawn(async move {
let res = enclave_start.handle(config_cpy).await?;
Ok::<Response, Error>(res)
});
Ok(shutdown_tx)
}
// TODO: do not shutdown if cli calls fail, just print
async fn deploy_and_handshake(
contract: Option<&str>,
args: &DevRequest,
config: &Config,
) -> Result<String, Error> {
info!("Waiting for enclave start to deploy contract and handshake");
// Wait at most 30 seconds to connect to enclave
let mut i = 30;
while CoreClient::connect(format!(
"{}:{}",
config.enclave_rpc_addr, config.enclave_rpc_port
))
.await
.is_err()
{
sleep(Duration::from_secs(1)).await;
i -= 1;
if i == 0 {
return Err(Error::GenericErr(
"Could not connect to enclave".to_string(),
));
}
}
// Calls which interact with enclave
info!("Successfully pinged enclave, enclave is running");
// Deploy contract IF existing contract wasn't pass into the function
let contract = if let Some(contract) = contract {
info!("Contract already deployed, reusing");
contract.to_string()
} else {
info!("Deploying contract");
// Deploy Contract request
let contract_deploy = ContractDeployRequest {
init_msg: args.init_msg.clone(),
label: args.label.clone(),
contract_manifest: args.contract_manifest.clone(),
};
// Call handler
let cd_res = contract_deploy.handle(config).await;
// Return contract address or shutdown enclave & error
match cd_res {
Ok(Response::ContractDeploy(res)) => res.contract_addr,
Err(e) => return Err(e),
_ => unreachable!("Unexpected response variant"),
}
};
// Run handshake
info!("Running handshake on contract `{}`", contract);
let handshake = HandshakeRequest {
contract: wasmaddr_to_id(&contract).map_err(|_| Error::GenericErr(String::default()))?,
use_latest_trusted: args.use_latest_trusted,
};
let h_res = handshake.handle(config).await;
match h_res {
Ok(Response::Handshake(res)) => {
info!("Handshake complete: {}", res.pub_key);
}
Err(e) => {
return Err(e);
}
_ => unreachable!("Unexpected response variant"),
};
Ok(contract)
}
async fn watcher(tx: mpsc::Sender<DevRebuild>, log_dir: PathBuf) -> Result<()> {
let wx = Watchexec::new_async(move |mut action| {
let tx = tx.clone();
Box::new(async move {
if action.signals().any(|sig| sig == Signal::Interrupt) {
eprintln!("[Quitting...]");
action.quit();
return action;
}
// Look for updates to filepaths
if let Some((path, _)) = action.paths().next() {
debug!("event triggered on path:\n {:?}", path);
if path
.file_name()
.expect("events can't trigger on nonexistent files")
.eq("enclave")
{
let _res = tx.send(DevRebuild::Enclave).await;
} else if path
.file_name()
.expect("events can't trigger on nonexistent files")
.eq("contract")
{
let _res = tx.send(DevRebuild::Contract).await;
}
}
action
})
})?;
// Start the engine
let main = wx.main();
// Watch all files in quartz app directory
// TODO: should create_log_dir be called instead? Just enforce building log in all cases?
wx.config.pathset([log_dir]);
// Keep running until Watchexec quits
let _ = main.await.into_diagnostic()?;
Ok(())
}

View file

@ -1,6 +1,6 @@
use std::process::Command;
use async_trait::async_trait; use async_trait::async_trait;
use color_eyre::owo_colors::OwoColorize;
use tokio::process::Command;
use tracing::{debug, info}; use tracing::{debug, info};
use crate::{ use crate::{
@ -21,20 +21,31 @@ impl Handler for EnclaveBuildRequest {
config: C, config: C,
) -> Result<Self::Response, Self::Error> { ) -> Result<Self::Response, Self::Error> {
let config = config.as_ref(); let config = config.as_ref();
info!("{}", "\nPeforming Enclave Build".blue().bold());
let enclave_dir = config.app_dir.join("enclave");
let mut cargo = Command::new("cargo"); let mut cargo = Command::new("cargo");
let command = cargo let command = cargo
.args(["build", "--release"]) .arg("build")
.args(["--manifest-path", &self.manifest_path.display().to_string()]); .args(["--target-dir", &config.app_dir.join("target").display().to_string()]) // TODO: Where should this be set to?
.args(["--manifest-path", &enclave_dir.join("Cargo.toml").display().to_string(),
]);
if config.mock_sgx { if config.mock_sgx {
debug!("Building with mock-sgx enabled"); debug!("Building with mock-sgx enabled");
command.arg("--features=mock-sgx"); command.arg("--features=mock-sgx");
} }
info!("🚧 Building enclave ..."); if self.release {
debug!("Targetting release");
command.arg("--release");
}
info!("{}", "🚧 Running build command ...".green().bold());
let status = command let status = command
.status() .status()
.await
.map_err(|e| Error::GenericErr(e.to_string()))?; .map_err(|e| Error::GenericErr(e.to_string()))?;
if !status.success() { if !status.success() {
@ -44,6 +55,8 @@ impl Handler for EnclaveBuildRequest {
))); )));
} }
config.log_build(true).await?;
Ok(EnclaveBuildResponse.into()) Ok(EnclaveBuildResponse.into())
} }
} }

View file

@ -1,13 +1,18 @@
use std::env; use std::{fs, path::Path};
use async_trait::async_trait; use async_trait::async_trait;
use tokio::process::Command; use cargo_metadata::MetadataCommand;
use color_eyre::owo_colors::OwoColorize;
use tokio::{
process::{Child, Command},
sync::watch,
};
use tracing::{debug, info}; use tracing::{debug, info};
use crate::{ use crate::{
config::Config, config::Config,
error::Error, error::Error,
handler::{utils::helpers::get_hash_height, Handler}, handler::{utils::helpers::write_cache_hash_height, Handler},
request::enclave_start::EnclaveStartRequest, request::enclave_start::EnclaveStartRequest,
response::{enclave_start::EnclaveStartResponse, Response}, response::{enclave_start::EnclaveStartResponse, Response},
}; };
@ -21,11 +26,13 @@ impl Handler for EnclaveStartRequest {
self, self,
config: C, config: C,
) -> Result<Self::Response, Self::Error> { ) -> Result<Self::Response, Self::Error> {
let mut config = config.as_ref().clone(); let config = config.as_ref().clone();
// Get trusted height and hash info!("{}", "\nPeforming Enclave Start".blue().bold());
let (trusted_height, trusted_hash) = get_hash_height(self.use_latest_trusted, &mut config)?;
let enclave_dir = config.app_dir.join("enclave"); // Get trusted height and hash
let (trusted_height, trusted_hash) = self.get_hash_height(&config)?;
println!("trusted height: {} hash: {}", trusted_height, trusted_hash);
write_cache_hash_height(trusted_height, trusted_hash, &config).await?;
if config.mock_sgx { if config.mock_sgx {
let enclave_args: Vec<String> = vec![ let enclave_args: Vec<String> = vec![
@ -38,65 +45,121 @@ impl Handler for EnclaveStartRequest {
]; ];
// Run quartz enclave and block // Run quartz enclave and block
let _res = run_enclave( let enclave_child =
enclave_dir.join("Cargo.toml").display().to_string(), create_mock_enclave_child(config.app_dir.as_path(), config.release, enclave_args)
config.mock_sgx, .await?;
enclave_args, handle_process(self.shutdown_rx, enclave_child).await?;
} else {
let enclave_dir = fs::canonicalize(config.app_dir.join("enclave"))?;
// gramine private key
gramine_sgx_gen_private_key(&enclave_dir).await?;
// gramine manifest
let quartz_dir_canon = &enclave_dir.join("..");
gramine_manifest(
&trusted_height.to_string(),
&trusted_hash.to_string(),
quartz_dir_canon,
&enclave_dir,
) )
.await?; .await?;
} else {
// set cwd to enclave app
env::set_current_dir(enclave_dir).map_err(|e| Error::GenericErr(e.to_string()))?;
// gramine private key
gramine_sgx_gen_private_key().await?;
// gramine manifest
gramine_manifest(&trusted_height.to_string(), &trusted_hash.to_string()).await?;
// gramine sign // gramine sign
gramine_sgx_sign().await?; gramine_sgx_sign(&enclave_dir).await?;
// Run quartz enclave and block // Run quartz enclave and block
gramine_sgx().await?; let enclave_child = create_gramine_sgx_child(&enclave_dir).await?;
handle_process(self.shutdown_rx, enclave_child).await?;
} }
Ok(EnclaveStartResponse.into()) Ok(EnclaveStartResponse.into())
} }
} }
async fn run_enclave( async fn handle_process(
manifest_path: String, shutdown_rx: Option<watch::Receiver<()>>,
mock_sgx: bool, mut child: Child,
enclave_args: Vec<String>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut cargo = Command::new("cargo"); info!("{}", "Running enclave ...".green().bold());
let command = cargo.args(["run", "--release", "--manifest-path", &manifest_path]); match shutdown_rx {
Some(mut rx) => {
if mock_sgx { tokio::select! {
debug!("Running with mock-sgx enabled"); status = child.wait() => {
command.arg("--features=mock-sgx"); handle_child_status(status.map_err(|e| Error::GenericErr(e.to_string()))?)?;
} }
_ = rx.changed() => {
command.arg("--"); info!("Enclave shutdown signal received.");
command.args(enclave_args); let _ = child.kill().await;
}
println!("command: {:?}", command); }
}
info!("🚧 Running enclave ..."); None => {
let status = command // If no shutdown receiver is provided, just wait for the child process
.status() let status = child
.wait()
.await .await
.map_err(|e| Error::GenericErr(e.to_string()))?; .map_err(|e| Error::GenericErr(e.to_string()))?;
handle_child_status(status)?;
}
}
Ok(())
}
async fn create_mock_enclave_child(
app_dir: &Path,
release: bool,
enclave_args: Vec<String>,
) -> Result<Child, Error> {
let enclave_dir = app_dir.join("enclave");
let target_dir = app_dir.join("target");
// Use the enclave package metadata to get the path to the program binary
let package_name = MetadataCommand::new()
.manifest_path(&enclave_dir.join("Cargo.toml"))
.exec()
.map_err(|e| Error::GenericErr(e.to_string()))?
.root_package()
.ok_or("No root package found in the metadata")
.map_err(|e| Error::GenericErr(e.to_string()))?
.name
.clone();
let executable = if release {
target_dir.join("release").join(package_name)
} else {
target_dir.join("debug").join(package_name)
};
let mut command = Command::new(executable.display().to_string());
command.args(enclave_args);
debug!("Enclave Start Command: {:?}", command);
info!("{}", "🚧 Spawning enclave process ...".green().bold());
let child = command
.spawn()
.map_err(|e| Error::GenericErr(e.to_string()))?;
Ok(child)
}
fn handle_child_status(status: std::process::ExitStatus) -> Result<(), Error> {
if !status.success() { if !status.success() {
return Err(Error::GenericErr(format!( return Err(Error::GenericErr(format!(
"Couldn't build enclave. {:?}", "Couldn't build enclave. {:?}",
status status
))); )));
} }
Ok(()) Ok(())
} }
async fn gramine_sgx_gen_private_key() -> Result<(), Error> { async fn gramine_sgx_gen_private_key(enclave_dir: &Path) -> Result<(), Error> {
// Launch the gramine-sgx-gen-private-key command // Launch the gramine-sgx-gen-private-key command
Command::new("gramine-sgx-gen-private-key") Command::new("gramine-sgx-gen-private-key")
.current_dir(enclave_dir)
.output() .output()
.await .await
.map_err(|e| { .map_err(|e| {
@ -111,9 +174,12 @@ async fn gramine_sgx_gen_private_key() -> Result<(), Error> {
Ok(()) Ok(())
} }
async fn gramine_manifest(trusted_height: &str, trusted_hash: &str) -> Result<(), Error> { async fn gramine_manifest(
let current_dir = env::current_dir().map_err(|e| Error::GenericErr(e.to_string()))?; trusted_height: &str,
trusted_hash: &str,
quartz_dir: &Path,
enclave_dir: &Path,
) -> Result<(), Error> {
let host = target_lexicon::HOST; let host = target_lexicon::HOST;
let arch_libdir = format!( let arch_libdir = format!(
"/lib/{}-{}-{}", "/lib/{}-{}-{}",
@ -133,11 +199,12 @@ async fn gramine_manifest(trusted_height: &str, trusted_hash: &str) -> Result<()
.arg("-Dra_type=epid") .arg("-Dra_type=epid")
.arg(format!("-Dra_client_spid={}", ra_client_spid)) .arg(format!("-Dra_client_spid={}", ra_client_spid))
.arg("-Dra_client_linkable=1") .arg("-Dra_client_linkable=1")
.arg(format!("-Dquartz_dir={}", current_dir.display())) .arg(format!("-Dquartz_dir={}", quartz_dir.display().to_string()))
.arg(format!("-Dtrusted_height={}", trusted_height)) .arg(format!("-Dtrusted_height={}", trusted_height))
.arg(format!("-Dtrusted_hash={}", trusted_hash)) .arg(format!("-Dtrusted_hash={}", trusted_hash))
.arg("quartz.manifest.template") .arg("quartz.manifest.template")
.arg("quartz.manifest") .arg("quartz.manifest")
.current_dir(enclave_dir)
.status() .status()
.await .await
.map_err(|e| Error::GenericErr(e.to_string()))?; .map_err(|e| Error::GenericErr(e.to_string()))?;
@ -152,12 +219,13 @@ async fn gramine_manifest(trusted_height: &str, trusted_hash: &str) -> Result<()
Ok(()) Ok(())
} }
async fn gramine_sgx_sign() -> Result<(), Error> { async fn gramine_sgx_sign(enclave_dir: &Path) -> Result<(), Error> {
let status = Command::new("gramine-sgx-sign") let status = Command::new("gramine-sgx-sign")
.arg("--manifest") .arg("--manifest")
.arg("quartz.manifest") .arg("quartz.manifest")
.arg("--output") .arg("--output")
.arg("quartz.manifest.sgx") .arg("quartz.manifest.sgx")
.current_dir(enclave_dir)
.status() .status()
.await .await
.map_err(|e| Error::GenericErr(e.to_string()))?; .map_err(|e| Error::GenericErr(e.to_string()))?;
@ -172,19 +240,13 @@ async fn gramine_sgx_sign() -> Result<(), Error> {
Ok(()) Ok(())
} }
async fn gramine_sgx() -> Result<(), Error> { async fn create_gramine_sgx_child(enclave_dir: &Path) -> Result<Child, Error> {
let status = Command::new("gramine-sgx") info!("🚧 Spawning enclave process ...");
let child = Command::new("gramine-sgx")
.arg("./quartz") .arg("./quartz")
.status() .current_dir(enclave_dir)
.await .spawn()?;
.map_err(|e| Error::GenericErr(e.to_string()))?;
if !status.success() { Ok(child)
return Err(Error::GenericErr(format!(
"gramine-sgx-sign command failed. {:?}",
status
)));
}
Ok(())
} }

View file

@ -1,26 +1,26 @@
use std::{env::current_dir, fs, str::FromStr}; use std::{fs, str::FromStr};
use anyhow::anyhow; use anyhow::anyhow;
use async_trait::async_trait; use async_trait::async_trait;
use color_eyre::owo_colors::OwoColorize;
use cosmrs::tendermint::chain::Id as ChainId; // TODO see if this redundancy in dependencies can be decreased use cosmrs::tendermint::chain::Id as ChainId; // TODO see if this redundancy in dependencies can be decreased
use cycles_sync::wasmd_client::{CliWasmdClient, WasmdClient};
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
use reqwest::Url; use reqwest::Url;
use serde::Serialize;
use serde_json::json; use serde_json::json;
use tendermint_rpc::{query::EventType, HttpClient, SubscriptionClient, WebSocketClient}; use tendermint_rpc::{query::EventType, HttpClient, SubscriptionClient, WebSocketClient};
use tm_prover::{config::Config as TmProverConfig, prover::prove}; use tm_prover::{config::Config as TmProverConfig, prover::prove};
use tracing::{debug, info, trace}; use tracing::{debug, info};
use wasmd_client::{CliWasmdClient, WasmdClient};
use super::utils::{ use super::utils::{helpers::block_tx_commit, types::WasmdTxResponse};
helpers::{block_tx_commit, run_relay},
types::WasmdTxResponse,
};
use crate::{ use crate::{
config::Config, config::Config,
error::Error, error::Error,
handler::{ handler::{
utils::{helpers::get_hash_height, types::RelayMessage}, utils::{
helpers::{read_cached_hash_height, run_relay_rust},
types::RelayMessage,
},
Handler, Handler,
}, },
request::handshake::HandshakeRequest, request::handshake::HandshakeRequest,
@ -38,7 +38,7 @@ impl Handler for HandshakeRequest {
) -> Result<Self::Response, Self::Error> { ) -> Result<Self::Response, Self::Error> {
let config = config.as_ref().clone(); let config = config.as_ref().clone();
trace!("starting handshake..."); info!("{}", "\nPeforming Handshake".blue().bold());
// TODO: may need to import verbosity here // TODO: may need to import verbosity here
let pub_key = handshake(self, config) let pub_key = handshake(self, config)
@ -49,25 +49,19 @@ impl Handler for HandshakeRequest {
} }
} }
#[derive(Serialize)] async fn handshake(args: HandshakeRequest, config: Config) -> Result<String, anyhow::Error> {
struct Message<'a> {
message: &'a str,
}
async fn handshake(args: HandshakeRequest, mut config: Config) -> Result<String, anyhow::Error> {
let httpurl = Url::parse(&format!("http://{}", config.node_url))?; let httpurl = Url::parse(&format!("http://{}", config.node_url))?;
let wsurl = format!("ws://{}/websocket", config.node_url); let wsurl = format!("ws://{}/websocket", config.node_url);
let tmrpc_client = HttpClient::new(httpurl.as_str())?; let tmrpc_client = HttpClient::new(httpurl.as_str())?;
let wasmd_client = CliWasmdClient::new(Url::parse(httpurl.as_str())?); let wasmd_client = CliWasmdClient::new(Url::parse(httpurl.as_str())?);
let (trusted_height, trusted_hash) = get_hash_height(false, &mut config)?; let (trusted_height, trusted_hash) = read_cached_hash_height(&config).await?;
// TODO: dir logic issue #125
let base_path = current_dir()?.join("../");
info!("Running SessionCreate"); info!("Running SessionCreate");
let res: serde_json::Value = run_relay(
base_path.as_path(), let res: serde_json::Value = run_relay_rust(
config.enclave_rpc(),
config.mock_sgx, config.mock_sgx,
RelayMessage::SessionCreate, RelayMessage::SessionCreate,
) )
@ -94,8 +88,7 @@ async fn handshake(args: HandshakeRequest, mut config: Config) -> Result<String,
info!("Waiting 2 blocks for light client proof"); info!("Waiting 2 blocks for light client proof");
two_block_waitoor(&wsurl).await?; two_block_waitoor(&wsurl).await?;
// TODO: dir logic issue #125 let proof_path = config.cache_dir()?.join("light-client-proof.json");
let proof_path = current_dir()?.join("../utils/tm-prover/light-client-proof.json");
debug!("Proof path: {:?}", proof_path.to_str()); debug!("Proof path: {:?}", proof_path.to_str());
// Call tm prover with trusted hash and height // Call tm prover with trusted hash and height
@ -118,16 +111,13 @@ async fn handshake(args: HandshakeRequest, mut config: Config) -> Result<String,
// Read proof file // Read proof file
let proof = fs::read_to_string(proof_path.as_path())?; let proof = fs::read_to_string(proof_path.as_path())?;
let proof_json = serde_json::to_string(&Message {
message: proof.trim(),
})?;
// Execute SessionSetPubKey on enclave // Execute SessionSetPubKey on enclave
info!("Running SessionSetPubKey"); info!("Running SessionSetPubKey");
let res: serde_json::Value = run_relay( let res: serde_json::Value = run_relay_rust(
base_path.as_path(), config.enclave_rpc(),
config.mock_sgx, config.mock_sgx,
RelayMessage::SessionSetPubKey(proof_json), RelayMessage::SessionSetPubKey(proof.trim().to_string()),
) )
.await?; .await?;

View file

@ -2,8 +2,9 @@ use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use cargo_generate::{generate, GenerateArgs, TemplatePath, Vcs}; use cargo_generate::{generate, GenerateArgs, TemplatePath, Vcs};
use color_eyre::owo_colors::OwoColorize;
use tokio::fs; use tokio::fs;
use tracing::trace; use tracing::info;
use crate::{ use crate::{
config::Config, config::Config,
@ -18,13 +19,13 @@ impl Handler for InitRequest {
type Error = Error; type Error = Error;
type Response = Response; type Response = Response;
// TODO: Add non-template init method
async fn handle<C: AsRef<Config> + Send>( async fn handle<C: AsRef<Config> + Send>(
self, self,
config: C, config: C,
) -> Result<Self::Response, Self::Error> { ) -> Result<Self::Response, Self::Error> {
let config = config.as_ref(); let config = config.as_ref();
info!("{}", "\nPeforming Init".blue().bold());
trace!("initializing directory structure...");
let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".."); let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
@ -61,6 +62,7 @@ impl Handler for InitRequest {
.display() .display()
.to_string(); .to_string();
info!("\n{}", "It's TEE time.".green().bold());
Ok(InitResponse { result_dir }.into()) Ok(InitResponse { result_dir }.into())
} }
} }

View file

@ -1,21 +1,38 @@
use std::{path::Path, time::Duration}; use std::time::Duration;
use anyhow::anyhow; use anyhow::anyhow;
use cosmrs::{AccountId, ErrorReport}; use cosmrs::{AccountId, ErrorReport};
use cycles_sync::wasmd_client::{CliWasmdClient, WasmdClient}; use cosmwasm_std::Binary;
use hex::decode;
use quartz_common::{
contract::msg::{
execute::{session_create::RawSessionCreate, session_set_pub_key::RawSessionSetPubKey},
instantiate::RawCoreInstantiate,
},
proto::{
core_client::CoreClient, InstantiateRequest, SessionCreateRequest, SessionSetPubKeyRequest,
},
};
use quartz_tee_ra::{intel_sgx::epid::types::ReportBody, IASReport};
use regex::Regex; use regex::Regex;
use reqwest::Url; use reqwest::Url;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde_json::json;
use subtle_encoding::bech32::decode as bech32_decode; use subtle_encoding::bech32::decode as bech32_decode;
use tendermint::{block::Height, Hash}; use tendermint::{block::Height, Hash};
use tendermint_rpc::{ use tendermint_rpc::{
endpoint::tx::Response as TmTxResponse, error::ErrorDetail, Client, HttpClient, endpoint::tx::Response as TmTxResponse, error::ErrorDetail, Client, HttpClient,
}; };
use tokio::{fs, process::Command}; use tokio::{
fs::{self, File},
io::AsyncWriteExt,
process::Command,
};
use tracing::debug; use tracing::debug;
use wasmd_client::{CliWasmdClient, WasmdClient};
use super::types::RelayMessage; use super::types::RelayMessage;
use crate::{config::Config, error}; use crate::{config::Config, error::Error};
pub fn wasmaddr_to_id(address_str: &str) -> Result<AccountId, anyhow::Error> { pub fn wasmaddr_to_id(address_str: &str) -> Result<AccountId, anyhow::Error> {
let (hr, _) = bech32_decode(address_str).map_err(|e| anyhow!(e))?; let (hr, _) = bech32_decode(address_str).map_err(|e| anyhow!(e))?;
@ -26,36 +43,6 @@ pub fn wasmaddr_to_id(address_str: &str) -> Result<AccountId, anyhow::Error> {
address_str.parse().map_err(|e: ErrorReport| anyhow!(e)) address_str.parse().map_err(|e: ErrorReport| anyhow!(e))
} }
// TODO: move wrapping result with "quartz:" struct into here
pub async fn run_relay<R: DeserializeOwned>(
base_path: &Path,
mock_sgx: bool,
msg: RelayMessage,
) -> Result<R, anyhow::Error> {
let relayer_path = base_path.join("cli/scripts/relay.sh");
let mut bash = Command::new("bash");
let command = bash
.arg(relayer_path)
.arg(msg.to_string())
.env("MOCK_SGX", mock_sgx.to_string());
if let RelayMessage::SessionSetPubKey(proof) = msg {
command.arg(proof);
}
let output = command.output().await?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
}
let query_result: R = serde_json::from_slice(&output.stdout)
.map_err(|e| anyhow!("Error deserializing: {}", e))?;
Ok(query_result)
}
// Note: time until tx commit is empiraclly 800ms on DO wasmd chain. // Note: time until tx commit is empiraclly 800ms on DO wasmd chain.
pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result<TmTxResponse, anyhow::Error> { pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result<TmTxResponse, anyhow::Error> {
let re = Regex::new(r"tx \([A-F0-9]{64}\) not found")?; let re = Regex::new(r"tx \([A-F0-9]{64}\) not found")?;
@ -90,37 +77,15 @@ pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result<TmTxRespon
} }
} }
/// Returns the trusted hash and height
pub fn get_hash_height(
use_latest: bool,
config: &mut Config,
) -> Result<(Height, Hash), error::Error> {
if use_latest || config.trusted_height == 0 || config.trusted_hash.is_empty() {
let (trusted_height, trusted_hash) = latest_height_hash(&config.node_url)?;
config.trusted_hash = trusted_hash.to_string();
config.trusted_height = trusted_height.into();
Ok((trusted_height, trusted_hash))
} else {
Ok((
config.trusted_height.try_into()?,
config
.trusted_hash
.parse()
.map_err(|_| error::Error::GenericErr("invalid hash".to_string()))?,
))
}
}
// Queries the chain for the latested height and hash // Queries the chain for the latested height and hash
pub fn latest_height_hash(node_url: &String) -> Result<(Height, Hash), error::Error> { pub fn query_latest_height_hash(node_url: &String) -> Result<(Height, Hash), Error> {
let httpurl = Url::parse(&format!("http://{}", node_url)) let httpurl = Url::parse(&format!("http://{}", node_url))
.map_err(|e| error::Error::GenericErr(e.to_string()))?; .map_err(|e| Error::GenericErr(e.to_string()))?;
let wasmd_client = CliWasmdClient::new(httpurl); let wasmd_client = CliWasmdClient::new(httpurl);
let (trusted_height, trusted_hash) = wasmd_client let (trusted_height, trusted_hash) = wasmd_client
.trusted_height_hash() .trusted_height_hash()
.map_err(|e| error::Error::GenericErr(e.to_string()))?; .map_err(|e| Error::GenericErr(e.to_string()))?;
Ok(( Ok((
trusted_height.try_into()?, trusted_height.try_into()?,
@ -128,17 +93,223 @@ pub fn latest_height_hash(node_url: &String) -> Result<(Height, Hash), error::Er
)) ))
} }
pub async fn persist_config_hash_height(config: &Config) -> Result<(), error::Error> { pub async fn write_cache_hash_height(
let config_path = config.app_dir.join("quartz.toml"); trusted_height: Height,
trusted_hash: Hash,
config: &Config,
) -> Result<(), Error> {
let height_path = config.cache_dir()?.join("trusted.height");
fs::write(height_path.as_path(), trusted_height.to_string()).await?;
let toml_content = fs::read_to_string(&config_path).await?; let hash_path = config.cache_dir()?.join("trusted.hash");
let mut written_config: Config = toml::from_str(&toml_content)?; fs::write(hash_path.as_path(), trusted_hash.to_string()).await?;
written_config.trusted_hash.clone_from(&config.trusted_hash);
written_config.trusted_height = config.trusted_height;
let toml_string = toml::to_string(config)?;
fs::write(&config_path, toml_string).await?;
Ok(()) Ok(())
} }
pub async fn read_cached_hash_height(config: &Config) -> Result<(Height, Hash), Error> {
let height_path = config.cache_dir()?.join("trusted.height");
let hash_path = config.cache_dir()?.join("trusted.hash");
if !height_path.exists() {
return Err(Error::PathNotFile(height_path.display().to_string()));
}
if !hash_path.exists() {
return Err(Error::PathNotFile(hash_path.display().to_string()));
}
let trusted_height: Height = fs::read_to_string(height_path.as_path()).await?.parse()?;
let trusted_hash: Hash = fs::read_to_string(hash_path.as_path()).await?.parse()?;
Ok((trusted_height, trusted_hash))
}
async fn run_docker_command(quote: &[u8]) -> Result<(String, String), Error> {
let dir = tempfile::tempdir()?;
let ias_api_key: &str = "669244b3e6364b5888289a11d2a1726d";
let ra_client_spid: &str = "51CAF5A48B450D624AEFE3286D314894";
let quote_file_path = dir.path().join("test.quote");
let datareport_file_path = dir.path().join("datareport");
let datareportsig_file_path = dir.path().join("datareportsig");
let mut quote_file = File::create(quote_file_path.clone()).await?;
quote_file.write_all(quote).await?;
let status = Command::new("docker")
.arg("run")
.arg("--rm")
.arg("-it")
.arg("-v")
.arg("/tmp:/tmp:rw")
.arg("gramineproject/gramine:1.7-jammy")
.arg(format!(
"gramine-sgx-ias-request report -g \"{}\" -k \"{}\" -q \"{}\" -r \"{}\" -s \"{}\" > /dev/null 2>&1",
ra_client_spid, ias_api_key, quote_file_path.display().to_string(), datareport_file_path.display().to_string(), datareportsig_file_path.display().to_string()
))
.status()
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
if !status.success() {
return Err(Error::GenericErr(
"Failed to run docker command".to_string(),
));
}
let report = fs::read_to_string(datareport_file_path)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?;
let reportsig = fs::read_to_string(datareportsig_file_path)
.await
.map_err(|e| Error::GenericErr(e.to_string()))?
.replace("\r", "");
Ok((report, reportsig))
}
// TODO: move wrapping result with "quartz:" struct into here
pub async fn run_relay_rust<R: DeserializeOwned>(
enclave_rpc: String,
mock_sgx: bool,
relay_msg: RelayMessage,
) -> Result<R, anyhow::Error> {
// Query the gRPC quartz enclave service
let mut qc_client = CoreClient::connect(enclave_rpc).await?;
let attested_msg = match &relay_msg {
RelayMessage::Instantiate => &qc_client
.instantiate(tonic::Request::new(InstantiateRequest {}))
.await?
.get_ref()
.message
.clone(),
RelayMessage::SessionCreate => &qc_client
.session_create(tonic::Request::new(SessionCreateRequest {}))
.await?
.get_ref()
.message
.clone(),
RelayMessage::SessionSetPubKey(proof) => &qc_client
.session_set_pub_key(SessionSetPubKeyRequest {
message: proof.to_string(),
})
.await?
.get_ref()
.message
.clone(),
};
let mut attested_msg_json: serde_json::Value = serde_json::from_str(attested_msg)?;
let quote = attested_msg_json["quote"].take();
if mock_sgx {
match relay_msg {
RelayMessage::Instantiate => {
// Construct CoreInstantiate
let msg: RawCoreInstantiate = serde_json::from_value(attested_msg_json)?;
let query_result: R = serde_json::from_value(json!({
"msg": RawCoreInstantiate::from(msg),
"attestation": quote
}))?;
return Ok(query_result);
}
RelayMessage::SessionCreate => {
// Convert RelayMessage to a snake_case string
let request_key = relay_msg.to_string();
let msg: RawSessionCreate = serde_json::from_value(attested_msg_json)?;
// Build the nested JSON structures
let jsonify = json!({
"quartz": {
request_key: {
"msg": msg,
"attestation": quote
}
}
});
let query_result: R = serde_json::from_value(jsonify)?;
return Ok(query_result);
}
RelayMessage::SessionSetPubKey(_) => {
// Convert RelayMessage to a snake_case string
let request_key = relay_msg.to_string();
let msg: RawSessionSetPubKey = serde_json::from_value(attested_msg_json)?;
// Build the nested JSON structures
let jsonify = json!({
"quartz": {
request_key: {
"msg": msg,
"attestation": quote
}
}
});
let query_result: R = serde_json::from_value(jsonify)?;
return Ok(query_result);
}
}
}
// else
// docker
let quote_str = quote
.as_str()
.ok_or_else(|| Error::GenericErr("quote is not a string".to_string()))?;
let quote = decode(quote_str).map_err(|e| Error::GenericErr(e.to_string()))?;
println!("quote: {:?}", quote_str);
let (report, report_sig) = run_docker_command(&quote).await?;
match relay_msg {
RelayMessage::Instantiate => {
let msg: RawCoreInstantiate = serde_json::from_value(attested_msg_json)?;
let report_json: ReportBody = serde_json::from_str(&report)?;
let report_sig = report_sig.replace('\n', "");
let ias_report = IASReport {
report: report_json,
report_sig: Binary::from_base64(&report_sig)?,
};
// Nest the report inside another "report" field
let jsonify = json!({
"msg": msg,
"attestation": {
"report": {
"report": json!(ias_report.report),
"reportsig": ias_report.report_sig
}
}
});
let query_result: R = serde_json::from_value(jsonify)?;
return Ok(query_result);
}
RelayMessage::SessionCreate | RelayMessage::SessionSetPubKey(_) => {
// Convert request to snake_case
let request_key = relay_msg.to_string();
// Construct the JSON structure for SessionCreate or SessionSetPubKey
let jsonify = json!({
"quartz": {
request_key: {
"msg": attested_msg_json,
"attestation": {
"report": report,
"reportsig": report_sig
}
}
}
});
let query_result: R = serde_json::from_value(jsonify)?;
return Ok(query_result);
}
}
}

View file

@ -49,9 +49,9 @@ pub enum RelayMessage {
impl std::fmt::Display for RelayMessage { impl std::fmt::Display for RelayMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
RelayMessage::Instantiate => write!(f, "Instantiate"), RelayMessage::Instantiate => write!(f, "instantiate"),
RelayMessage::SessionCreate => write!(f, "SessionCreate"), RelayMessage::SessionCreate => write!(f, "session_create"),
RelayMessage::SessionSetPubKey(_) => write!(f, "SessionSetPubKey"), RelayMessage::SessionSetPubKey(_) => write!(f, "session_set_pub_key"),
} }
} }
} }

View file

@ -13,6 +13,7 @@
unused_qualifications unused_qualifications
)] )]
pub mod cache;
pub mod cli; pub mod cli;
pub mod config; pub mod config;
pub mod error; pub mod error;
@ -24,7 +25,7 @@ use std::path::PathBuf;
use clap::Parser; use clap::Parser;
use cli::ToFigment; use cli::ToFigment;
use color_eyre::eyre::Result; use color_eyre::{eyre::Result, owo_colors::OwoColorize};
use config::Config; use config::Config;
use figment::{ use figment::{
providers::{Env, Format, Serialized, Toml}, providers::{Env, Format, Serialized, Toml},
@ -50,7 +51,7 @@ const BANNER: &str = r"
async fn main() -> Result<()> { async fn main() -> Result<()> {
color_eyre::install()?; color_eyre::install()?;
println!("{BANNER}"); println!("{}", BANNER.yellow().bold());
let args: Cli = Cli::parse(); let args: Cli = Cli::parse();
check_path(&args.app_dir)?; check_path(&args.app_dir)?;

View file

@ -3,13 +3,14 @@ use crate::{
error::Error, error::Error,
request::{ request::{
contract_build::ContractBuildRequest, contract_deploy::ContractDeployRequest, contract_build::ContractBuildRequest, contract_deploy::ContractDeployRequest,
enclave_build::EnclaveBuildRequest, enclave_start::EnclaveStartRequest, dev::DevRequest, enclave_build::EnclaveBuildRequest, enclave_start::EnclaveStartRequest,
handshake::HandshakeRequest, init::InitRequest, handshake::HandshakeRequest, init::InitRequest,
}, },
}; };
pub mod contract_build; pub mod contract_build;
pub mod contract_deploy; pub mod contract_deploy;
pub mod dev;
pub mod enclave_build; pub mod enclave_build;
pub mod enclave_start; pub mod enclave_start;
pub mod handshake; pub mod handshake;
@ -23,6 +24,7 @@ pub enum Request {
ContractDeploy(ContractDeployRequest), ContractDeploy(ContractDeployRequest),
EnclaveBuild(EnclaveBuildRequest), EnclaveBuild(EnclaveBuildRequest),
EnclaveStart(EnclaveStartRequest), EnclaveStart(EnclaveStartRequest),
Dev(DevRequest),
} }
impl TryFrom<Command> for Request { impl TryFrom<Command> for Request {
@ -33,10 +35,21 @@ impl TryFrom<Command> for Request {
Command::Init(args) => Ok(InitRequest { name: args.name }.try_into()?), Command::Init(args) => Ok(InitRequest { name: args.name }.try_into()?),
Command::Handshake(args) => Ok(HandshakeRequest { Command::Handshake(args) => Ok(HandshakeRequest {
contract: args.contract, contract: args.contract,
use_latest_trusted: args.use_latest_trusted,
} }
.into()), .into()),
Command::Contract { contract_command } => contract_command.try_into(), Command::Contract { contract_command } => contract_command.try_into(),
Command::Enclave { enclave_command } => enclave_command.try_into(), Command::Enclave { enclave_command } => enclave_command.try_into(),
Command::Dev(args) => Ok(DevRequest {
watch: args.watch,
use_latest_trusted: args.use_latest_trusted,
init_msg: serde_json::from_str(&args.contract_deploy.init_msg)
.map_err(|e| Error::GenericErr(e.to_string()))?,
label: args.contract_deploy.label,
contract_manifest: args.contract_deploy.contract_manifest,
release: args.enclave_build.release,
}
.into()),
} }
} }
} }
@ -47,25 +60,29 @@ impl TryFrom<ContractCommand> for Request {
fn try_from(cmd: ContractCommand) -> Result<Request, Error> { fn try_from(cmd: ContractCommand) -> Result<Request, Error> {
match cmd { match cmd {
ContractCommand::Deploy(args) => { ContractCommand::Deploy(args) => {
if !args.wasm_bin_path.exists() { if !args.contract_manifest.exists() {
return Err(Error::PathNotFile(args.wasm_bin_path.display().to_string())); return Err(Error::PathNotFile(
args.contract_manifest.display().to_string(),
));
} }
Ok(ContractDeployRequest { Ok(ContractDeployRequest {
init_msg: serde_json::from_str(&args.init_msg) init_msg: serde_json::from_str(&args.init_msg)
.map_err(|e| Error::GenericErr(e.to_string()))?, .map_err(|e| Error::GenericErr(e.to_string()))?,
label: args.label, label: args.label,
wasm_bin_path: args.wasm_bin_path, contract_manifest: args.contract_manifest,
} }
.into()) .into())
} }
ContractCommand::Build(args) => { ContractCommand::Build(args) => {
if !args.manifest_path.exists() { if !args.contract_manifest.exists() {
return Err(Error::PathNotFile(args.manifest_path.display().to_string())); return Err(Error::PathNotFile(
args.contract_manifest.display().to_string(),
));
} }
Ok(ContractBuildRequest { Ok(ContractBuildRequest {
manifest_path: args.manifest_path, contract_manifest: args.contract_manifest,
} }
.into()) .into())
} }
@ -79,10 +96,11 @@ impl TryFrom<EnclaveCommand> for Request {
fn try_from(cmd: EnclaveCommand) -> Result<Request, Error> { fn try_from(cmd: EnclaveCommand) -> Result<Request, Error> {
match cmd { match cmd {
EnclaveCommand::Build(args) => Ok(EnclaveBuildRequest { EnclaveCommand::Build(args) => Ok(EnclaveBuildRequest {
manifest_path: args.manifest_path, release: args.release,
} }
.into()), .into()),
EnclaveCommand::Start(args) => Ok(EnclaveStartRequest { EnclaveCommand::Start(args) => Ok(EnclaveStartRequest {
shutdown_rx: None,
use_latest_trusted: args.use_latest_trusted, use_latest_trusted: args.use_latest_trusted,
} }
.into()), .into()),

View file

@ -4,7 +4,7 @@ use crate::request::Request;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ContractBuildRequest { pub struct ContractBuildRequest {
pub manifest_path: PathBuf, pub contract_manifest: PathBuf,
} }
impl From<ContractBuildRequest> for Request { impl From<ContractBuildRequest> for Request {

View file

@ -8,7 +8,7 @@ use crate::{error::Error, request::Request};
pub struct ContractDeployRequest { pub struct ContractDeployRequest {
pub init_msg: serde_json::Value, pub init_msg: serde_json::Value,
pub label: String, pub label: String,
pub wasm_bin_path: PathBuf, pub contract_manifest: PathBuf,
} }
impl From<ContractDeployRequest> for Request { impl From<ContractDeployRequest> for Request {

19
cli/src/request/dev.rs Normal file
View file

@ -0,0 +1,19 @@
use std::path::PathBuf;
use crate::request::Request;
#[derive(Clone, Debug)]
pub struct DevRequest {
pub watch: bool,
pub use_latest_trusted: bool,
pub init_msg: serde_json::Value,
pub label: String,
pub contract_manifest: PathBuf,
pub release: bool,
}
impl From<DevRequest> for Request {
fn from(request: DevRequest) -> Self {
Self::Dev(request)
}
}

View file

@ -1,10 +1,8 @@
use std::path::PathBuf;
use crate::request::Request; use crate::request::Request;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct EnclaveBuildRequest { pub struct EnclaveBuildRequest {
pub manifest_path: PathBuf, pub release: bool,
} }
impl From<EnclaveBuildRequest> for Request { impl From<EnclaveBuildRequest> for Request {

View file

@ -1,7 +1,15 @@
use crate::request::Request; use tendermint::{block::Height, Hash};
use tokio::sync::watch;
use tracing::debug;
use crate::{
config::Config, error::Error, handler::utils::helpers::query_latest_height_hash,
request::Request,
};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct EnclaveStartRequest { pub struct EnclaveStartRequest {
pub shutdown_rx: Option<watch::Receiver<()>>,
pub use_latest_trusted: bool, pub use_latest_trusted: bool,
} }
@ -10,3 +18,24 @@ impl From<EnclaveStartRequest> for Request {
Self::EnclaveStart(request) Self::EnclaveStart(request)
} }
} }
impl EnclaveStartRequest {
/// Returns the trusted hash and height
pub fn get_hash_height(&self, config: &Config) -> Result<(Height, Hash), Error> {
if self.use_latest_trusted || config.trusted_height == 0 || config.trusted_hash.is_empty() {
debug!("querying latest trusted hash & height from node");
let (trusted_height, trusted_hash) = query_latest_height_hash(&config.node_url)?;
Ok((trusted_height, trusted_hash))
} else {
debug!("reusing config trusted hash & height");
Ok((
config.trusted_height.try_into()?,
config
.trusted_hash
.parse()
.map_err(|_| Error::GenericErr("invalid hash".to_string()))?,
))
}
}
}

View file

@ -5,6 +5,7 @@ use crate::request::Request;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct HandshakeRequest { pub struct HandshakeRequest {
pub contract: AccountId, pub contract: AccountId,
pub use_latest_trusted: bool,
} }
impl From<HandshakeRequest> for Request { impl From<HandshakeRequest> for Request {

View file

@ -2,12 +2,13 @@ use serde::Serialize;
use crate::response::{ use crate::response::{
contract_build::ContractBuildResponse, contract_deploy::ContractDeployResponse, contract_build::ContractBuildResponse, contract_deploy::ContractDeployResponse,
enclave_build::EnclaveBuildResponse, enclave_start::EnclaveStartResponse, dev::DevResponse, enclave_build::EnclaveBuildResponse, enclave_start::EnclaveStartResponse,
handshake::HandshakeResponse, init::InitResponse, handshake::HandshakeResponse, init::InitResponse,
}; };
pub mod contract_build; pub mod contract_build;
pub mod contract_deploy; pub mod contract_deploy;
pub mod dev;
pub mod enclave_build; pub mod enclave_build;
pub mod enclave_start; pub mod enclave_start;
pub mod handshake; pub mod handshake;
@ -21,4 +22,5 @@ pub enum Response {
ContractDeploy(ContractDeployResponse), ContractDeploy(ContractDeployResponse),
EnclaveBuild(EnclaveBuildResponse), EnclaveBuild(EnclaveBuildResponse),
EnclaveStart(EnclaveStartResponse), EnclaveStart(EnclaveStartResponse),
Dev(DevResponse),
} }

12
cli/src/response/dev.rs Normal file
View file

@ -0,0 +1,12 @@
use serde::Serialize;
use crate::response::Response;
#[derive(Clone, Debug, Serialize)]
pub struct DevResponse;
impl From<DevResponse> for Response {
fn from(response: DevResponse) -> Self {
Self::Dev(response)
}
}

View file

@ -0,0 +1,20 @@
[package]
name = "wasmd-client"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
authors.workspace = true
[lib]
path="src/lib.rs"
[dependencies]
anyhow.workspace = true
cosmrs.workspace = true
hex.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true

View file

@ -244,9 +244,11 @@ impl WasmdClient for CliWasmdClient {
serde_json::from_slice(&output.stdout).unwrap_or_default(); serde_json::from_slice(&output.stdout).unwrap_or_default();
let trusted_height = query_result["SyncInfo"]["latest_block_height"] let trusted_height = query_result["SyncInfo"]["latest_block_height"]
.as_u64() .as_str()
.ok_or(anyhow!("Could not query height"))?; .ok_or(anyhow!("Could not query height"))?;
let trusted_height = trusted_height.parse::<u64>()?;
let trusted_hash = query_result["SyncInfo"]["latest_block_hash"] let trusted_hash = query_result["SyncInfo"]["latest_block_hash"]
.as_str() .as_str()
.ok_or(anyhow!("Could not query height"))? .ok_or(anyhow!("Could not query height"))?

View file

@ -9,13 +9,6 @@ repository.workspace = true
keywords = ["blockchain", "cosmos", "tendermint", "cycles", "quartz"] keywords = ["blockchain", "cosmos", "tendermint", "cycles", "quartz"]
readme = "README.md" readme = "README.md"
[lib]
path = "src/lib.rs"
[[bin]]
name = "submit"
path = "src/bin/submit.rs"
[dependencies] [dependencies]
# external # external
async-trait.workspace = true async-trait.workspace = true
@ -41,6 +34,8 @@ cosmwasm-std.workspace = true
# quartz # quartz
cw-tee-mtcs = { path = "../../apps/mtcs/contracts/cw-tee-mtcs/" } cw-tee-mtcs = { path = "../../apps/mtcs/contracts/cw-tee-mtcs/" }
wasmd-client.workspace = true
mtcs-enclave.workspace = true
[dev-dependencies] [dev-dependencies]
rand_core.workspace = true rand_core.workspace = true

View file

@ -1,174 +0,0 @@
use std::str::FromStr;
use anyhow::anyhow;
use bip32::secp256k1::{
ecdsa::VerifyingKey,
sha2::{Digest, Sha256},
};
use clap::Parser;
use cosmrs::{tendermint::chain::Id as TmChainId, AccountId};
use cosmwasm_std::{Addr, HexBinary};
use cw_tee_mtcs::state::{LiquiditySource, LiquiditySourceType};
use cycles_sync::{
types::{
ContractObligation, RawEncryptedObligation, RawSetOff, SubmitObligationsMsg,
SubmitObligationsMsgInner,
},
wasmd_client::{CliWasmdClient, WasmdClient},
};
use reqwest::Url;
use serde::{Deserialize, Serialize};
use subtle_encoding::bech32::decode as bech32_decode;
// const MNEMONIC_PHRASE: &str = "clutch debate vintage foster barely primary clown leader sell manual leopard ladder wet must embody story oyster imitate cable alien six square rice wedding";
const ADDRESS_PREFIX: &str = "wasm";
type Sha256Digest = [u8; 32];
#[derive(Clone, Debug, Serialize, Deserialize)]
struct QueryAllSetoffsResponse {
setoffs: Vec<(HexBinary, RawSetOff)>,
}
#[derive(Clone, Debug, Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[arg(short, long, value_parser = wasmaddr_to_id)]
mtcs: AccountId,
#[arg(short, long)]
epoch_pk: String,
#[arg(short, long)]
overdraft: String,
#[clap(long)]
flip: bool,
#[arg(
short,
long,
default_value = "wasm14qdftsfk6fwn40l0xmruga08xlczl4g05npy70"
)]
admin: String,
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let cli = Cli::parse();
let mut alice = Addr::unchecked("wasm124tuy67a9dcvfgcr4gjmz60syd8ddaugl33v0n");
let mut bob = Addr::unchecked("wasm1ctkqmg45u85jnf5ur9796h7ze4hj6ep5y7m7l6");
let overdraft = Addr::unchecked(cli.overdraft);
if cli.flip {
let temp = alice.clone();
alice = bob;
bob = temp;
}
let alice_to_bob: ContractObligation = ContractObligation {
debtor: alice.clone(),
creditor: bob.clone(),
amount: 10,
salt: HexBinary::from([0; 64]),
};
let bob_acceptance: ContractObligation = ContractObligation {
debtor: bob.clone(),
creditor: overdraft.clone(),
amount: 10,
salt: HexBinary::from([0; 64]),
};
let alice_tender: ContractObligation = ContractObligation {
debtor: overdraft.clone(),
creditor: alice.clone(),
amount: 10,
salt: HexBinary::from([0; 64]),
};
let intents = vec![alice_to_bob, bob_acceptance, alice_tender];
let epoch_pk = VerifyingKey::from_sec1_bytes(&hex::decode(cli.epoch_pk).unwrap()).unwrap();
let intents_enc = encrypt_overdraft_intents(intents, &epoch_pk);
let liquidity_sources: Vec<LiquiditySource> = vec![LiquiditySource {
address: overdraft,
source_type: LiquiditySourceType::Overdraft,
}];
let msg = create_wasm_msg(intents_enc, liquidity_sources)?;
let node_url = Url::parse("http://143.244.186.205:26657")?;
let chain_id = TmChainId::from_str("testing")?;
let wasmd_client = CliWasmdClient::new(node_url);
wasmd_client.tx_execute(&cli.mtcs, &chain_id, 3000000, &cli.admin.to_string(), msg)?;
Ok(())
}
pub struct OverdraftObligation {
pub debtor: Addr,
pub creditor: Addr,
pub amount: u64,
}
fn encrypt_overdraft_intents(
intents: Vec<ContractObligation>,
epoch_pk: &VerifyingKey,
) -> Vec<(Sha256Digest, Vec<u8>)> {
let mut intents_enc = vec![];
for i in intents {
// serialize intent
let i_ser = serde_json::to_string(&i).unwrap();
// encrypt intent
let i_cipher = ecies::encrypt(&epoch_pk.to_sec1_bytes(), i_ser.as_bytes()).unwrap();
// hash intent
let i_digest: Sha256Digest = {
let mut hasher = Sha256::new();
hasher.update(i_ser);
hasher.finalize().into()
};
intents_enc.push((i_digest, i_cipher));
}
intents_enc
}
fn create_wasm_msg(
obligations_enc: Vec<(Sha256Digest, Vec<u8>)>,
liquidity_sources: Vec<LiquiditySource>,
) -> anyhow::Result<serde_json::Value> {
let obligations_enc: Vec<_> = obligations_enc
.into_iter()
.map(|(digest, ciphertext)| {
let digest = HexBinary::from(digest);
let ciphertext = HexBinary::from(ciphertext);
RawEncryptedObligation { digest, ciphertext }
})
.collect();
let msg = SubmitObligationsMsg {
submit_obligations: SubmitObligationsMsgInner {
obligations: obligations_enc,
liquidity_sources,
},
};
serde_json::to_value(msg).map_err(Into::into)
}
fn wasmaddr_to_id(address_str: &str) -> anyhow::Result<AccountId> {
let (hr, _) = bech32_decode(address_str).map_err(|e| anyhow!(e))?;
if hr != ADDRESS_PREFIX {
return Err(anyhow!(hr));
}
Ok(address_str.parse().unwrap())
}

View file

@ -1,99 +0,0 @@
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use cosmrs::{tendermint::chain::Id, AccountId};
use displaydoc::Display;
use reqwest::Url;
use subtle_encoding::{bech32::decode as bech32_decode, Error as Bech32DecodeError};
use thiserror::Error;
use uuid::Uuid;
const ADDRESS_PREFIX: &str = "wasm";
#[derive(Clone, Debug, Parser)]
#[command(author, version, about)]
pub struct Cli {
/// Increase output logging verbosity to debug level.
#[arg(short, long)]
pub verbose: bool,
/// The host to which to bind the API server.
#[arg(short = 'N', long, default_value = "http://127.0.0.1:26657")]
pub node: Url,
/// Obligato API server.
#[arg(long, default_value = "https://bisenzone.obligato.network")]
pub obligato_url: Url,
/// Obligato key.
#[arg(
long,
default_value = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNhZXNzdGpjdG16bXVqaW55cGJlIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcwMzAxOTE0OSwiZXhwIjoyMDE4NTk1MTQ5fQ.EV6v5J3dz8WHAdTK4_IEisKzF-n1Gqyn4wCce_Zrqf4"
)]
pub obligato_key: String,
/// Path to output CSV file
#[arg(short, long)]
pub keys_file: PathBuf,
/// Path to obligation-user map
#[arg(short, long)]
pub obligation_user_map_file: PathBuf,
/// Chain-id of MTCS chain
#[arg(long, default_value = "testing")]
pub chain_id: Id,
/// Smart contract address
#[arg(short, long, value_parser = wasm_address)]
pub contract: AccountId,
/// tx sender address
#[arg(short, long)]
pub user: String,
/// Main command
#[command(subcommand)]
pub command: CliCommand,
}
#[derive(Clone, Debug, Subcommand)]
pub enum CliCommand {
/// Sync obligations
SyncObligations {
/// epoch pk
#[arg(short, long)]
epoch_pk: String,
/// liquidity sources' UUIDs
#[arg(short, long, num_args = 1.., value_parser = parse_uuid)]
liquidity_sources: Vec<Uuid>,
},
/// Sync set-offs
SyncSetOffs,
/// Get address for Uuid
GetAddress {
#[arg(long, value_parser = parse_uuid)]
uuid: Uuid,
},
}
#[derive(Display, Error, Debug)]
pub enum AddressError {
/// Address is not bech32 encoded
NotBech32Encoded(#[source] Bech32DecodeError),
/// Human readable part mismatch (expected `wasm`, found {0})
HumanReadableMismatch(String),
}
fn wasm_address(address_str: &str) -> Result<AccountId, AddressError> {
let (hr, _) = bech32_decode(address_str).map_err(AddressError::NotBech32Encoded)?;
if hr != ADDRESS_PREFIX {
return Err(AddressError::HumanReadableMismatch(hr));
}
Ok(address_str.parse().unwrap())
}
fn parse_uuid(uuid_str: &str) -> Result<Uuid, String> {
Uuid::parse_str(uuid_str).map_err(|e| e.to_string())
}

View file

@ -1,4 +0,0 @@
pub mod cli;
pub mod obligato_client;
pub mod types;
pub mod wasmd_client;

View file

@ -1,237 +1,129 @@
use std::{ use std::str::FromStr;
collections::{HashMap, HashSet},
error::Error,
fs::File,
io::{BufReader, BufWriter, Write},
path::PathBuf,
str::FromStr,
};
use bip32::{ use anyhow::anyhow;
secp256k1::{ use bip32::secp256k1::{
ecdsa::VerifyingKey, ecdsa::VerifyingKey,
sha2::{Digest, Sha256}, sha2::{Digest, Sha256},
},
Error as Bip32Error, Language, Mnemonic, Prefix, PrivateKey, Seed, XPrv,
}; };
use clap::Parser; use clap::Parser;
use cosmrs::{tendermint::account::Id as TmAccountId, AccountId}; use cosmrs::{tendermint::chain::Id as TmChainId, AccountId};
use cosmwasm_std::HexBinary; use cosmwasm_std::{Addr, HexBinary};
use serde::{Deserialize, Serialize}; use cw_tee_mtcs::state::{LiquiditySource, LiquiditySourceType};
use serde_json::json; use mtcs_enclave::types::{
use tracing::{debug, Level}; ContractObligation, RawEncryptedObligation, RawSetOff, SubmitObligationsMsg,
use uuid::Uuid; SubmitObligationsMsgInner,
use crate::{
cli::{Cli, CliCommand},
obligato_client::{http::HttpClient, Client},
types::{
Obligation, ObligatoObligation, ObligatoSetOff, RawEncryptedObligation, RawObligation,
RawOffset, RawSetOff, SubmitObligatioMsg, SubmitObligatoObligationsMsgInner,
},
wasmd_client::{CliWasmdClient, QueryResult, WasmdClient},
}; };
use reqwest::Url;
use serde::{Deserialize, Serialize};
use subtle_encoding::bech32::decode as bech32_decode;
use wasmd_client::{CliWasmdClient, WasmdClient};
mod cli; // const MNEMONIC_PHRASE: &str = "clutch debate vintage foster barely primary clown leader sell manual leopard ladder wet must embody story oyster imitate cable alien six square rice wedding";
mod obligato_client;
mod types;
mod wasmd_client;
const MNEMONIC_PHRASE: &str = "clutch debate vintage foster barely primary clown leader sell manual leopard ladder wet must embody story oyster imitate cable alien six square rice wedding";
const ADDRESS_PREFIX: &str = "wasm"; const ADDRESS_PREFIX: &str = "wasm";
type Sha256Digest = [u8; 32]; type Sha256Digest = [u8; 32];
type DynError = Box<dyn Error>;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
struct QueryAllSetoffsResponse { struct QueryAllSetoffsResponse {
setoffs: Vec<(HexBinary, RawSetOff)>, setoffs: Vec<(HexBinary, RawSetOff)>,
} }
#[derive(Clone, Debug, Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[arg(short, long, value_parser = wasmaddr_to_id)]
mtcs: AccountId,
#[arg(short, long)]
epoch_pk: String,
#[arg(short, long)]
overdraft: String,
#[clap(long)]
flip: bool,
#[arg(
short,
long,
default_value = "wasm14qdftsfk6fwn40l0xmruga08xlczl4g05npy70"
)]
admin: String,
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), DynError> { async fn main() -> Result<(), anyhow::Error> {
let cli = Cli::parse(); let cli = Cli::parse();
tracing_subscriber::fmt() let mut alice = Addr::unchecked("wasm124tuy67a9dcvfgcr4gjmz60syd8ddaugl33v0n");
.with_max_level(if cli.verbose { let mut bob = Addr::unchecked("wasm1ctkqmg45u85jnf5ur9796h7ze4hj6ep5y7m7l6");
Level::DEBUG let overdraft = Addr::unchecked(cli.overdraft);
} else {
Level::ERROR
})
.with_level(true)
.with_writer(std::io::stderr)
.init();
match cli.command { if cli.flip {
CliCommand::SyncObligations { let temp = alice.clone();
ref epoch_pk, alice = bob;
ref liquidity_sources, bob = temp;
} => sync_obligations(cli.clone(), epoch_pk, liquidity_sources).await?,
CliCommand::SyncSetOffs => sync_setoffs(cli).await?,
CliCommand::GetAddress { uuid } => address_from_uuid(uuid)?,
} }
Ok(()) let alice_to_bob: ContractObligation = ContractObligation {
} debtor: alice.clone(),
creditor: bob.clone(),
fn address_from_uuid(uuid: Uuid) -> Result<(), DynError> { amount: 10,
let seed = global_seed()?; salt: HexBinary::from([0; 64]),
let sk = derive_child_xprv(&seed, uuid);
let pk_b = sk.public_key().public_key().to_sec1_bytes();
let pk = VerifyingKey::from_sec1_bytes(&pk_b)?;
println!("{}", wasm_address(pk));
Ok(())
}
fn wasm_address(pk: VerifyingKey) -> String {
let tm_pk = TmAccountId::from(pk);
AccountId::new(ADDRESS_PREFIX, tm_pk.as_bytes())
.unwrap()
.to_string()
}
fn global_seed() -> Result<Seed, Bip32Error> {
let mnemonic = Mnemonic::new(MNEMONIC_PHRASE, Language::English)?;
Ok(mnemonic.to_seed("password"))
}
async fn sync_setoffs(cli: Cli) -> Result<(), DynError> {
let wasmd_client = CliWasmdClient::new(cli.node);
let query_result: QueryResult<QueryAllSetoffsResponse> =
wasmd_client.query_smart(&cli.contract, json!("get_all_setoffs"))?;
let setoffs = query_result.data.setoffs;
// read keys
let keys = read_keys_file(cli.keys_file)?;
let obligation_user_map = read_obligation_user_map_file(cli.obligation_user_map_file)?;
let setoffs: Vec<ObligatoSetOff> = setoffs
.iter()
.flat_map(|(obligation_digest, so)| match so {
RawSetOff::SetOff(sos_enc) => {
let so_enc = sos_enc.first().unwrap();
let (debtor_id, creditor_id) =
obligation_user_map.get(obligation_digest).copied().unwrap();
let sk = |id| keys[&id].private_key().to_bytes();
let so_ser = if let Ok(so) = ecies::decrypt(&sk(debtor_id), so_enc.as_slice()) {
so
} else if let Ok(so) = ecies::decrypt(&sk(creditor_id), so_enc.as_slice()) {
so
} else {
unreachable!()
}; };
let so: RawOffset = serde_json::from_slice(&so_ser).unwrap(); let bob_acceptance: ContractObligation = ContractObligation {
Some(ObligatoSetOff { debtor: bob.clone(),
debtor_id, creditor: overdraft.clone(),
creditor_id, amount: 10,
amount: so.set_off, salt: HexBinary::from([0; 64]),
})
}
RawSetOff::Transfer(_) => None,
})
.collect();
debug!("setoffs: {setoffs:?}");
// send to Obligato
let client = HttpClient::new(cli.obligato_url, cli.obligato_key);
client.set_setoffs(setoffs).await?;
Ok(())
}
async fn sync_obligations(
cli: Cli,
epoch_pk: &str,
liquidity_sources: &[Uuid],
) -> Result<(), DynError> {
let mut intents = {
let client = HttpClient::new(cli.obligato_url.clone(), cli.obligato_key);
client
.get_obligations()
.await
.map_err(|_| cli.obligato_url.to_string())?
}; };
let keys = derive_keys(&mut intents, liquidity_sources)?; let alice_tender: ContractObligation = ContractObligation {
write_keys_to_file(cli.keys_file, &keys); debtor: overdraft.clone(),
creditor: alice.clone(),
add_default_acceptances(&mut intents, liquidity_sources); amount: 10,
salt: HexBinary::from([0; 64]),
debug!("intents: {intents:?}");
let intents_enc = {
let epoch_pk = VerifyingKey::from_sec1_bytes(&hex::decode(epoch_pk).unwrap()).unwrap();
encrypt_intents(intents, &keys, &epoch_pk, cli.obligation_user_map_file)
}; };
debug!("Encrypted {} intents", intents_enc.len());
let liquidity_sources = liquidity_sources let intents = vec![alice_to_bob, bob_acceptance, alice_tender];
.iter() let epoch_pk = VerifyingKey::from_sec1_bytes(&hex::decode(cli.epoch_pk).unwrap()).unwrap();
.map(|id| keys[id].private_key().public_key())
.collect(); let intents_enc = encrypt_overdraft_intents(intents, &epoch_pk);
let liquidity_sources: Vec<LiquiditySource> = vec![LiquiditySource {
address: overdraft,
source_type: LiquiditySourceType::Overdraft,
}];
let msg = create_wasm_msg(intents_enc, liquidity_sources)?; let msg = create_wasm_msg(intents_enc, liquidity_sources)?;
let wasmd_client = CliWasmdClient::new(cli.node);
wasmd_client.tx_execute(&cli.contract, &cli.chain_id, 3000000, &cli.user, msg)?; let node_url = Url::parse("http://143.244.186.205:26657")?;
let chain_id = TmChainId::from_str("testing")?;
let wasmd_client = CliWasmdClient::new(node_url);
wasmd_client.tx_execute(&cli.mtcs, &chain_id, 3000000, &cli.admin.to_string(), msg)?;
Ok(()) Ok(())
} }
fn create_wasm_msg( pub struct OverdraftObligation {
obligations_enc: Vec<(Sha256Digest, Vec<u8>)>, pub debtor: Addr,
liquidity_sources: Vec<VerifyingKey>, pub creditor: Addr,
) -> Result<serde_json::Value, DynError> { pub amount: u64,
let obligations_enc: Vec<_> = obligations_enc
.into_iter()
.map(|(digest, ciphertext)| {
let digest = HexBinary::from(digest);
let ciphertext = HexBinary::from(ciphertext);
RawEncryptedObligation { digest, ciphertext }
})
.collect();
let liquidity_sources = liquidity_sources
.into_iter()
.map(|pk| HexBinary::from(pk.to_sec1_bytes().as_ref()))
.collect();
let msg = SubmitObligatioMsg {
submit_obligations: SubmitObligatoObligationsMsgInner {
obligations: obligations_enc,
liquidity_sources,
},
};
serde_json::to_value(msg).map_err(Into::into)
} }
fn encrypt_intents( fn encrypt_overdraft_intents(
intents: Vec<ObligatoObligation>, intents: Vec<ContractObligation>,
keys: &HashMap<Uuid, XPrv>,
epoch_pk: &VerifyingKey, epoch_pk: &VerifyingKey,
obligation_user_map_file: PathBuf,
) -> Vec<(Sha256Digest, Vec<u8>)> { ) -> Vec<(Sha256Digest, Vec<u8>)> {
let mut intents_enc = vec![]; let mut intents_enc = vec![];
let mut intent_user_map = HashMap::new();
for i in intents { for i in intents {
// create an intent
let ro = {
let o = Obligation {
debtor: keys[&i.debtor_id].private_key().public_key(),
creditor: keys[&i.creditor_id].private_key().public_key(),
amount: i.amount,
salt: [0; 64],
};
RawObligation::from(o)
};
// serialize intent // serialize intent
let i_ser = serde_json::to_string(&ro).unwrap(); let i_ser = serde_json::to_string(&i).unwrap();
// encrypt intent // encrypt intent
let i_cipher = ecies::encrypt(&epoch_pk.to_sec1_bytes(), i_ser.as_bytes()).unwrap(); let i_cipher = ecies::encrypt(&epoch_pk.to_sec1_bytes(), i_ser.as_bytes()).unwrap();
@ -244,167 +136,38 @@ fn encrypt_intents(
}; };
intents_enc.push((i_digest, i_cipher)); intents_enc.push((i_digest, i_cipher));
intent_user_map.insert(HexBinary::from(i_digest), (i.debtor_id, i.creditor_id));
} }
write_obligation_user_map_to_file(obligation_user_map_file, &intent_user_map);
intents_enc intents_enc
} }
fn add_default_acceptances(obligations: &mut Vec<ObligatoObligation>, liquidity_sources: &[Uuid]) { fn create_wasm_msg(
let acceptances = obligations.iter().fold(HashSet::new(), |mut acc, o| { obligations_enc: Vec<(Sha256Digest, Vec<u8>)>,
if !liquidity_sources.contains(&o.debtor_id) { liquidity_sources: Vec<LiquiditySource>,
for ls in liquidity_sources { ) -> anyhow::Result<serde_json::Value> {
let acceptance = ObligatoObligation { let obligations_enc: Vec<_> = obligations_enc
id: Default::default(),
debtor_id: o.creditor_id,
creditor_id: *ls,
amount: u32::MAX as u64,
};
acc.insert(acceptance);
}
}
acc
});
obligations.extend(acceptances.into_iter().collect::<Vec<_>>());
}
fn read_keys_file(keys_file: PathBuf) -> Result<HashMap<Uuid, XPrv>, DynError> {
let keys_file = File::open(keys_file)?;
let keys_reader = BufReader::new(keys_file);
let keys: HashMap<Uuid, String> = serde_json::from_reader(keys_reader)?;
Ok(keys
.into_iter() .into_iter()
.map(|(id, key_str)| (id, XPrv::from_str(&key_str).unwrap())) .map(|(digest, ciphertext)| {
.collect()) let digest = HexBinary::from(digest);
} let ciphertext = HexBinary::from(ciphertext);
RawEncryptedObligation { digest, ciphertext }
fn write_keys_to_file(output_file: PathBuf, keys: &HashMap<Uuid, XPrv>) { })
let keys_str: HashMap<_, _> = keys
.iter()
.map(|(id, k)| (id, k.to_string(Prefix::XPRV).to_string()))
.collect(); .collect();
let output_file = File::create(output_file).expect("create file"); let msg = SubmitObligationsMsg {
let mut output_reader = BufWriter::new(output_file); submit_obligations: SubmitObligationsMsgInner {
output_reader obligations: obligations_enc,
.write_all(serde_json::to_string(&keys_str).unwrap().as_bytes()) liquidity_sources,
.expect("write file"); },
};
serde_json::to_value(msg).map_err(Into::into)
} }
fn read_obligation_user_map_file( fn wasmaddr_to_id(address_str: &str) -> anyhow::Result<AccountId> {
file: PathBuf, let (hr, _) = bech32_decode(address_str).map_err(|e| anyhow!(e))?;
) -> Result<HashMap<HexBinary, (Uuid, Uuid)>, DynError> { if hr != ADDRESS_PREFIX {
let map_file = File::open(file)?; return Err(anyhow!(hr));
let map_reader = BufReader::new(map_file);
serde_json::from_reader(map_reader).map_err(Into::into)
} }
fn write_obligation_user_map_to_file( Ok(address_str.parse().unwrap())
output_file: PathBuf,
obligation_user_map: &HashMap<HexBinary, (Uuid, Uuid)>,
) {
let output_file = File::create(output_file).expect("create file");
let mut output_reader = BufWriter::new(output_file);
output_reader
.write_all(
serde_json::to_string(&obligation_user_map)
.unwrap()
.as_bytes(),
)
.expect("write file");
}
fn derive_keys(
obligations: &mut Vec<ObligatoObligation>,
liquidity_sources: &[Uuid],
) -> Result<HashMap<Uuid, XPrv>, DynError> {
// Derive a BIP39 seed value using the given password
let seed = global_seed()?;
obligations.sort_by_key(|o| o.debtor_id);
let mut keys = HashMap::new();
for ls in liquidity_sources {
keys.entry(*ls)
.or_insert_with(|| derive_child_xprv(&seed, *ls));
}
for o in obligations {
keys.entry(o.debtor_id)
.or_insert_with(|| derive_child_xprv(&seed, o.debtor_id));
keys.entry(o.creditor_id)
.or_insert_with(|| derive_child_xprv(&seed, o.creditor_id));
}
Ok(keys)
}
fn derive_child_xprv(seed: &Seed, uuid: Uuid) -> XPrv {
// Hash the UUID using SHA-256
let mut hasher = Sha256::new();
hasher.update(uuid.as_bytes());
let uuid_digest = hasher.finalize();
// Convert the hash bytes to a number
let uuid_digest_num = u128::from_be_bytes(uuid_digest[..16].try_into().unwrap());
// Take modulo (2^31 - 1)
let address_index = uuid_digest_num % ((1u128 << 31) - 1);
let child_path = format!("m/0/44'/118'/0'/0/{address_index}")
.parse()
.unwrap();
let child_xprv = XPrv::derive_from_path(seed, &child_path);
child_xprv.unwrap()
}
#[cfg(test)]
mod tests {
use std::{error::Error, str::FromStr};
use bip32::{Mnemonic, Prefix, PrivateKey, XPrv};
use rand_core::OsRng;
use uuid::Uuid;
use crate::{derive_child_xprv, global_seed};
#[test]
fn test_create_mnemonic() {
// Generate random Mnemonic using the default language (English)
let mnemonic = Mnemonic::random(OsRng, Default::default());
println!("{}", mnemonic.phrase());
}
#[test]
fn test_enc_dec_for_derived() -> Result<(), Box<dyn Error>> {
let seed = global_seed()?;
let alice_uuid = Uuid::from_u128(1);
let alice_sk = derive_child_xprv(&seed, alice_uuid);
let alice_pk = alice_sk.private_key().public_key();
assert_eq!(
alice_pk.to_sec1_bytes(),
hex::decode("0219b0b8ee5fe9b317b69119fd15170d79737380c4f020e251b7839096f5513ccf")
.unwrap()
.into()
);
let alice_sk_str = alice_sk.to_string(Prefix::XPRV).to_string();
assert_eq!(XPrv::from_str(&alice_sk_str).unwrap(), alice_sk);
let msg = r#"{"debtor":"02027e3510f66f1f6c1ea5e3600062255928e518220f7883810cac3fc7fc092057","creditor":"0216254f4636c4e68ae22d98538851a46810b65162fe37bf57cba6d563617c913e","amount":10,"salt":"65c188bcc133add598f7eecc449112f4bf61024345316cff0eb5ce61291991b141073dcd3c543ea142e66fffa8f483dc382043d37e490ef9b8069c489ce94a0b"}"#;
let ciphertext = ecies::encrypt(&alice_pk.to_sec1_bytes(), msg.as_bytes()).unwrap();
// println!("{}", hex::encode(&ciphertext));
let msg_dec =
ecies::decrypt(&alice_sk.private_key().to_bytes(), ciphertext.as_slice()).unwrap();
assert_eq!(msg, String::from_utf8(msg_dec).unwrap().as_str());
Ok(())
}
} }

View file

@ -1,73 +0,0 @@
use async_trait::async_trait;
use reqwest::Url;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tracing::debug;
use crate::{
obligato_client::Client,
types::{ObligatoObligation, ObligatoSetOff},
};
pub struct HttpClient {
client: reqwest::Client,
url: Url,
key: String,
}
impl HttpClient {
pub fn new(url: Url, key: String) -> Self {
Self {
client: reqwest::Client::new(),
url,
key,
}
}
fn url_with_path(&self, path: &str) -> Url {
let mut url = self.url.clone();
url.set_path(path);
url
}
}
#[async_trait]
impl Client for HttpClient {
type Error = reqwest::Error;
async fn get_obligations(&self) -> Result<Vec<ObligatoObligation>, Self::Error> {
let response = self
.client
.post(self.url_with_path("api/sync/obligations2contract"))
.json(&json!({"denom_id": "1", "key": self.key }))
.send()
.await?
.json::<GetObligationsResponse>()
.await?;
Ok(response.all_obligations.obligations)
}
async fn set_setoffs(&self, setoffs: Vec<ObligatoSetOff>) -> Result<(), Self::Error> {
let response = self
.client
.post(self.url_with_path("api/set-offs"))
.json(&setoffs)
.send()
.await?;
debug!("{}", response.text().await.unwrap());
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct GetObligationsInnerResponse {
obligations: Vec<ObligatoObligation>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct GetObligationsResponse {
#[serde(rename = "allObligations")]
all_obligations: GetObligationsInnerResponse,
}

View file

@ -1,40 +0,0 @@
use async_trait::async_trait;
use uuid::Uuid;
use crate::{
obligato_client::Client,
types::{ObligatoObligation, ObligatoSetOff},
};
pub struct MockClient {
pub bank: Uuid,
}
#[async_trait]
impl Client for MockClient {
type Error = ();
async fn get_obligations(&self) -> Result<Vec<ObligatoObligation>, Self::Error> {
Ok(vec![
// obligation: 1 --10--> 2
ObligatoObligation {
id: Uuid::from_u128(1),
debtor_id: Uuid::from_u128(1),
creditor_id: Uuid::from_u128(2),
amount: 10,
},
// tender: $ --10--> 1
ObligatoObligation {
id: Uuid::from_u128(2),
debtor_id: self.bank,
creditor_id: Uuid::from_u128(1),
amount: 10,
},
])
}
async fn set_setoffs(&self, setoffs: Vec<ObligatoSetOff>) -> Result<(), Self::Error> {
println!("{:?}", setoffs);
Ok(())
}
}

View file

@ -1,15 +0,0 @@
use async_trait::async_trait;
use crate::types::{ObligatoObligation, ObligatoSetOff};
pub mod http;
pub mod mock;
#[async_trait]
pub trait Client {
type Error;
async fn get_obligations(&self) -> Result<Vec<ObligatoObligation>, Self::Error>;
async fn set_setoffs(&self, setoffs: Vec<ObligatoSetOff>) -> Result<(), Self::Error>;
}

View file

@ -1,107 +0,0 @@
use bip32::secp256k1::ecdsa::VerifyingKey;
use cosmwasm_std::{Addr, HexBinary};
use cw_tee_mtcs::state::LiquiditySource;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Eq, Hash)]
pub struct ObligatoObligation {
pub id: Uuid,
pub debtor_id: Uuid,
pub creditor_id: Uuid,
pub amount: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ContractObligation {
pub debtor: Addr,
pub creditor: Addr,
pub amount: u64,
#[serde(default)]
pub salt: HexBinary,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawObligation {
pub debtor: HexBinary,
pub creditor: HexBinary,
pub amount: u64,
#[serde(default)]
pub salt: HexBinary,
}
#[derive(Clone, Debug)]
pub struct Obligation {
pub debtor: VerifyingKey,
pub creditor: VerifyingKey,
pub amount: u64,
pub salt: [u8; 64],
}
impl From<Obligation> for RawObligation {
fn from(obligation: Obligation) -> Self {
Self {
debtor: obligation.debtor.to_sec1_bytes().into_vec().into(),
creditor: obligation.creditor.to_sec1_bytes().into_vec().into(),
amount: obligation.amount,
salt: obligation.salt.into(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawEncryptedObligation {
pub digest: HexBinary,
pub ciphertext: HexBinary,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitObligationsMsg {
pub submit_obligations: SubmitObligationsMsgInner,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitObligationsMsgInner {
pub obligations: Vec<RawEncryptedObligation>,
pub liquidity_sources: Vec<LiquiditySource>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitObligatioMsg {
pub submit_obligations: SubmitObligatoObligationsMsgInner,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitObligatoObligationsMsgInner {
pub obligations: Vec<RawEncryptedObligation>,
pub liquidity_sources: Vec<HexBinary>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct ObligatoSetOff {
pub debtor_id: Uuid,
pub creditor_id: Uuid,
pub amount: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RawSetOff {
SetOff(Vec<HexBinary>),
Transfer(RawSetOffTransfer),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawSetOffTransfer {
pub payer: String,
pub payee: String,
pub amount: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawOffset {
pub debtor: HexBinary,
pub creditor: HexBinary,
pub amount: u64,
pub set_off: u64,
}

View file

@ -76,6 +76,7 @@ pub async fn prove(
.latest_trusted() .latest_trusted()
.ok_or_else(|| eyre!("No trusted state found for primary"))?; .ok_or_else(|| eyre!("No trusted state found for primary"))?;
info!("Getting status of node");
let status = client.status().await?; let status = client.status().await?;
let latest_height = status.sync_info.latest_block_height; let latest_height = status.sync_info.latest_block_height;
let latest_app_hash = status.sync_info.latest_app_hash; let latest_app_hash = status.sync_info.latest_app_hash;