diff --git a/.changelog/unreleased/breaking-changes/104-refactor-app-module.md b/.changelog/unreleased/breaking-changes/104-refactor-app-module.md new file mode 100644 index 00000000..9f944469 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/104-refactor-app-module.md @@ -0,0 +1,2 @@ +- Make `app` module more maintainable by re-organizing the code + ([#104](https://github.com/informalsystems/basecoin-rs/issues/104)) diff --git a/.changelog/unreleased/breaking-changes/105-refactor-cli.md b/.changelog/unreleased/breaking-changes/105-refactor-cli.md new file mode 100644 index 00000000..0fa6db76 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/105-refactor-cli.md @@ -0,0 +1,2 @@ +- Refactor CLI using `clap` to get more extensible + ([#105](https://github.com/informalsystems/basecoin-rs/issues/105)) diff --git a/.changelog/unreleased/breaking-changes/13-add-config-file.md b/.changelog/unreleased/breaking-changes/13-add-config-file.md new file mode 100644 index 00000000..5068d389 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/13-add-config-file.md @@ -0,0 +1,2 @@ +- Making the app user-configurable by including a config file + ([#13](https://github.com/informalsystems/basecoin-rs/issues/13)) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 627fe4fe..6d3bf150 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -28,10 +28,40 @@ jobs: args: --all -- --check # This job creates the "clippy-results" GitHub Action that lists the clippy results in a nice format. - clippy-json-output: + clippy-no-features: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cargo + target + key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-rust- + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + override: true + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --lib -- -Dwarnings -Drust-2018-idioms + + clippy-all-features: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cargo + target + key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-rust- - uses: actions-rs/toolchain@v1 with: toolchain: stable @@ -39,6 +69,5 @@ jobs: override: true - uses: actions-rs/clippy-check@v1 with: - name: clippy-results token: ${{ secrets.GITHUB_TOKEN }} args: --all-features --all-targets -- -Dwarnings -Drust-2018-idioms diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 55cabd43..a127aa5c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cargo + target + key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-rust- - uses: actions-rs/toolchain@v1 with: toolchain: stable @@ -25,7 +33,6 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --all-features ibc-integration: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index cf8637a5..c1f50f8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,12 +14,52 @@ dependencies = [ ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "anstream" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ - "winapi", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", ] [[package]] @@ -45,17 +85,6 @@ dependencies = [ "syn 2.0.15", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -113,6 +142,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.0" @@ -129,8 +164,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" name = "basecoin" version = "0.1.0" dependencies = [ - "base64", + "base64 0.21.0", "bytes", + "clap", "cosmrs", "derive_more", "displaydoc", @@ -144,13 +180,15 @@ dependencies = [ "prost", "rand", "serde", + "serde_derive", "serde_json", "sha2 0.10.6", - "structopt", "tendermint 0.31.1", "tendermint-abci", "tendermint-proto 0.31.1", + "tendermint-rpc", "tokio", + "toml 0.7.4", "tonic", "tower", "tower-abci", @@ -322,25 +360,75 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.34.0" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" dependencies = [ - "ansi_term", - "atty", + "anstream", + "anstyle", "bitflags", + "clap_lex", "strsim", - "textwrap", - "unicode-width", - "vec_map", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.15", ] +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "const-oid" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "cosmos-sdk-proto" version = "0.16.0" @@ -409,6 +497,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-logs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" +dependencies = [ + "sct", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -672,6 +769,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + [[package]] name = "funty" version = "2.0.0" @@ -840,23 +946,36 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.3.3" +name = "headers" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "unicode-segmentation", + "base64 0.13.1", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", ] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "headers-core" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "libc", + "http", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -945,6 +1064,43 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-proxy" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" +dependencies = [ + "bytes", + "futures", + "headers", + "http", + "hyper", + "hyper-rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", + "webpki", +] + +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "ct-logs", + "futures-util", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "webpki", + "webpki-roots", +] + [[package]] name = "hyper-timeout" version = "0.4.1" @@ -993,7 +1149,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f844ebc1ddf70f2ad7cd5981a2feba261fff55e1ad6482c27551ee3c6cec7df4" dependencies = [ - "base64", + "base64 0.21.0", "borsh", "bytes", "flex-error", @@ -1021,6 +1177,16 @@ dependencies = [ "sha3", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -1086,6 +1252,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1258,6 +1436,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "overload" version = "0.1.1" @@ -1328,6 +1512,33 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "peg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" + [[package]] name = "percent-encoding" version = "2.2.0" @@ -1410,7 +1621,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -1423,30 +1634,6 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.56" @@ -1616,6 +1803,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "ripemd" version = "0.1.3" @@ -1656,6 +1858,31 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.1", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls", + "schannel", + "security-framework", +] + [[package]] name = "rustversion" version = "1.0.12" @@ -1727,6 +1954,15 @@ dependencies = [ "safe-regex-compiler", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scale-info" version = "2.6.0" @@ -1751,12 +1987,31 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sec1" version = "0.3.0" @@ -1771,6 +2026,35 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.160" @@ -1822,6 +2106,26 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + [[package]] name = "sha2" version = "0.9.9" @@ -1915,6 +2219,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" version = "0.6.0" @@ -1943,33 +2253,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "structopt" -version = "0.3.26" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" @@ -2111,6 +2397,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "tendermint-config" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088bac81db392e66eedb508c400ebb2096d1e21cc40d71e3739a9bbd6838809a" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint 0.31.1", + "toml 0.5.11", + "url", +] + [[package]] name = "tendermint-light-client-verifier" version = "0.31.0" @@ -2161,12 +2461,47 @@ dependencies = [ ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "tendermint-rpc" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "120ccb296a0ec4485ddb78eb842e481d7a859d5659e0f1881456f8c127d5e950" dependencies = [ - "unicode-width", + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom", + "http", + "hyper", + "hyper-proxy", + "hyper-rustls", + "peg", + "pin-project", + "semver", + "serde", + "serde_bytes", + "serde_json", + "subtle", + "subtle-encoding", + "tendermint 0.31.1", + "tendermint-config", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "terminal_size" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +dependencies = [ + "rustix", + "windows-sys 0.48.0", ] [[package]] @@ -2225,6 +2560,21 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.27.0" @@ -2265,6 +2615,17 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-stream" version = "0.1.12" @@ -2313,19 +2674,36 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -2338,7 +2716,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64", + "base64 0.21.0", "bytes", "futures-core", "futures-util", @@ -2500,22 +2878,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] -name = "unicode-ident" -version = "1.0.8" +name = "unicode-bidi" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] -name = "unicode-segmentation" -version = "1.10.1" +name = "unicode-ident" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] -name = "unicode-width" -version = "0.1.10" +name = "unicode-normalization" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] [[package]] name = "unicode-xid" @@ -2524,16 +2905,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] -name = "valuable" -version = "0.1.0" +name = "untrusted" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] -name = "vec_map" +name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" @@ -2550,6 +2954,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -2620,6 +3034,35 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2636,12 +3079,36 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index 546df2f3..9e505ab0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,14 @@ description = """ default-run = "basecoin" + +[features] +default = [] +tower-abci = ["dep:tower-abci", "dep:tower"] + [dependencies] base64 = { version = "0.21", default-features = false, features = ["alloc"] } +clap = { version = "4.3.0", features = ["derive", "wrap_help"] } cosmrs = "0.11.0" displaydoc = { version = "0.2", default-features = false } derive_more = { version = "0.99.17", default-features = false, features = ["from", "into", "display"] } @@ -26,19 +32,21 @@ ics23 = { version = "=0.9.0", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["serde_no_std"] } prost = { version = "0.11.6", default-features = false } serde = "1.0" +serde_derive = { version = "1.0.104", default-features = false } serde_json = "1.0" sha2 = "0.10.2" -structopt = "0.3.21" tendermint = "=0.31.1" tendermint-abci = "=0.31.1" tendermint-proto = "=0.31.1" +tendermint-rpc = {version = "0.31.1", features = ["http-client"] } +toml = "0.7" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } tonic = "0.9" tracing = "0.1.26" tracing-subscriber = "0.3.16" -tower-abci = "0.7" -tower = { version = "0.4", features = ["full"]} +tower-abci = { version = "0.7", optional = true } +tower = { version = "0.4", features = ["full"], optional = true } [dev-dependencies] bytes = "1.0.1" diff --git a/ci/entrypoint.sh b/ci/entrypoint.sh index 693869b1..8c1dff5a 100755 --- a/ci/entrypoint.sh +++ b/ci/entrypoint.sh @@ -43,7 +43,7 @@ cometbft node > "${LOG_DIR}/cometbft.log" 2>&1 & echo "Starting basecoin-rs..." cd "${BASECOIN_SRC}" -"${BASECOIN_BIN}" -p 26358 -v > "${LOG_DIR}/basecoin.log" 2>&1 & +"${BASECOIN_BIN}" start --verbose > "${LOG_DIR}/basecoin.log" 2>&1 & echo "Waiting for CometBFT node to be available..." set +e diff --git a/ci/tests/token-transfer.sh b/ci/tests/token-transfer.sh index 7a632127..8bf463dc 100755 --- a/ci/tests/token-transfer.sh +++ b/ci/tests/token-transfer.sh @@ -8,7 +8,7 @@ hermes tx ft-transfer --dst-chain ibc-0 --src-chain basecoin-0 --src-port transf hermes tx ft-transfer --dst-chain basecoin-0 --src-chain ibc-0 --src-port transfer --src-channel channel-1 \ --amount 9999 --timeout-height-offset 1000 --number-msgs 2 -timeout 10 hermes start || [[ $? -eq 124 ]] +timeout 30 hermes start || [[ $? -eq 124 ]] hermes query packet pending-sends --chain ibc-0 --port transfer --channel channel-0 hermes query packet pending-sends --chain basecoin-0 --port transfer --channel channel-1 diff --git a/ci/tests/upgrade-client.sh b/ci/tests/upgrade-client.sh index 7a35cd60..e06eed74 100644 --- a/ci/tests/upgrade-client.sh +++ b/ci/tests/upgrade-client.sh @@ -1,18 +1,22 @@ #!/bin/bash set -euo pipefail +BASECOIN_BIN=${BASECOIN_BIN:-${HOME}/build/basecoin-rs/debug/basecoin} + +echo "Test client upgradability of basecoin-0 on ibc-0" + +hermes tx upgrade-chain --reference-chain basecoin-0 --host-chain ibc-0 --host-client 07-tendermint-0 --amount 10000000 --height-offset 20 +sleep 3s +plan_height=$("${BASECOIN_BIN}" query upgrade plan | grep -o 'height: [0-9]*' | awk '{print $2}') +echo "Waiting for upgrade plan to execute at height $plan_height..." +hermes upgrade client --host-chain ibc-0 --client 07-tendermint-0 --upgrade-height $plan_height + echo "Test client upgradability of ibc-0 on basecoin-0" hermes tx upgrade-chain --reference-chain ibc-0 --host-chain basecoin-0 --host-client 07-tendermint-0 --amount 10000000 --height-offset 35 +sleep 3s gaiad --node tcp://localhost:26657 tx gov vote 1 yes --home $HOME/data/ibc-0/data --keyring-backend test --keyring-dir $HOME/data/ibc-0 --chain-id ibc-0 --from validator --yes sleep 3s plan_height=$(gaiad --node tcp://localhost:26657 query gov proposal 1 --home $HOME/data/ibc-0/data | grep ' height:' | awk '{print $2}' | tr -d '"') echo "Waiting for upgrade plan to execute at height $plan_height..." hermes upgrade client --host-chain basecoin-0 --client 07-tendermint-0 --upgrade-height $plan_height - -echo "Test client upgradability of basecoin-0 on ibc-0" - -hermes tx upgrade-chain --reference-chain basecoin-0 --host-chain ibc-0 --host-client 07-tendermint-0 --amount 10000000 --height-offset 20 - -### TODO: uncomment below when we could extract the plan height -# hermes upgrade client --host-chain ibc-0 --client 07-tendermint-0 --upgrade-height $plan_height diff --git a/config.toml b/config.toml new file mode 100644 index 00000000..df0598f2 --- /dev/null +++ b/config.toml @@ -0,0 +1,29 @@ +# The global section has parameters that apply globally to Basecoin operations. +[global] + +# Specify the verbosity for basecoin logging output. Default: 'Info' +# Valid options are 'Error', 'Warn', 'Info', 'Debug', 'Trace'. +log_level = 'Debug' + +[server] + +# Bind TCP server to the this host +host = '127.0.0.1' + +# Bind TCP server to the this port +port = 26358 + +# Bind gRPC server to the this port +grpc_port = 9093 + +# Server read buffer size, in bytes, for each incoming client connection. +read_buf_size = 1048576 + + +[cometbft] + +# Specify the RPC address and port where the consensus RPC server listens on. +rpc_addr = 'http://127.0.0.1:26357' + +# Specify the GRPC address and port where the consensus GRPC server listens on. +grpc_addr = 'http://127.0.0.1:9090' \ No newline at end of file diff --git a/src/app.rs b/src/app.rs deleted file mode 100644 index b490c9dc..00000000 --- a/src/app.rs +++ /dev/null @@ -1,594 +0,0 @@ -use std::{ - convert::TryInto, - future::{self, Future}, - pin::Pin, - sync::{Arc, RwLock}, - task::{Context, Poll}, -}; - -use cosmrs::{ - tx::{SignerInfo, SignerPublicKey}, - AccountId, Tx, -}; -use ibc_proto::{ - cosmos::{ - base::tendermint::v1beta1::{ - service_server::Service as HealthService, AbciQueryRequest, AbciQueryResponse, - GetBlockByHeightRequest, GetBlockByHeightResponse, GetLatestBlockRequest, - GetLatestBlockResponse, GetLatestValidatorSetRequest, GetLatestValidatorSetResponse, - GetNodeInfoRequest, GetNodeInfoResponse, GetSyncingRequest, GetSyncingResponse, - GetValidatorSetByHeightRequest, GetValidatorSetByHeightResponse, - Module as VersionInfoModule, VersionInfo, - }, - tx::v1beta1::{ - service_server::Service as TxService, BroadcastTxRequest, BroadcastTxResponse, - GetBlockWithTxsRequest, GetBlockWithTxsResponse, GetTxRequest, GetTxResponse, - GetTxsEventRequest, GetTxsEventResponse, SimulateRequest, SimulateResponse, - }, - }, - google::protobuf::Any, -}; -use prost::Message; -use serde_json::Value; - -use tendermint::v0_37::abci::{ - request::Request as AbciRequest, response::Response as AbciResponse, -}; -use tendermint_abci::Application; -use tendermint_proto::{ - abci::{ - Event, RequestApplySnapshotChunk, RequestBeginBlock, RequestCheckTx, RequestDeliverTx, - RequestEcho, RequestEndBlock, RequestInfo, RequestInitChain, RequestLoadSnapshotChunk, - RequestOfferSnapshot, RequestQuery, ResponseBeginBlock, ResponseCommit, ResponseDeliverTx, - ResponseInfo, ResponseInitChain, ResponseQuery, - }, - crypto::{ProofOp, ProofOps}, - p2p::DefaultNodeInfo, -}; -use tonic::{Request, Response, Status}; -use tower::Service; -use tower_abci::BoxError; -use tracing::{debug, error, info}; - -use crate::{ - error::Error, - helper::macros::ResponseFromErrorExt, - helper::{Height, Identifier, Path}, - modules::{ - auth::account::ACCOUNT_PREFIX, - types::{IdentifiedModule, ModuleList, ModuleStore}, - Module, - }, - store::{MainStore, ProvableStore, RevertibleStore, SharedRw, SharedStore, Store}, -}; -pub(crate) const CHAIN_REVISION_NUMBER: u64 = 0; - -pub struct Builder { - store: MainStore, - modules: SharedRw>, -} - -impl Builder { - /// Constructor. - pub fn new(store: S) -> Self { - Self { - store: SharedStore::new(RevertibleStore::new(store)), - modules: Arc::new(RwLock::new(vec![])), - } - } - - /// Returns a share to the module's store if a module with specified identifier was previously - /// added, otherwise creates a new module store and returns it. - pub fn module_store(&self, prefix: &Identifier) -> SharedStore> { - let modules = self.modules.read().unwrap(); - modules - .iter() - .find(|m| &m.id == prefix) - .map(|IdentifiedModule { module, .. }| module.store().share()) - .unwrap_or_else(|| SharedStore::new(ModuleStore::new(S::default()))) - } - - #[inline] - fn is_unique_id(&self, prefix: &Identifier) -> bool { - !self.modules.read().unwrap().iter().any(|m| &m.id == prefix) - } - - /// Adds a new module. Panics if a module with the specified identifier was previously added. - pub fn add_module( - self, - prefix: Identifier, - module: impl Module> + 'static, - ) -> Self { - assert!(self.is_unique_id(&prefix), "module prefix must be unique"); - self.modules.write().unwrap().push(IdentifiedModule { - id: prefix, - module: Box::new(module), - }); - self - } - - pub fn build(self) -> BaseCoinApp { - BaseCoinApp { - store: self.store, - modules: self.modules, - } - } -} - -/// BaseCoin ABCI application. -/// -/// Can be safely cloned and sent across threads, but not shared. -#[derive(Clone)] -pub struct BaseCoinApp { - store: MainStore, - modules: SharedRw>, -} - -impl BaseCoinApp { - // try to deliver the message to all registered modules - // if `module.deliver()` returns `Error::NotHandled`, try next module - // Return: - // * other errors immediately OR - // * `Error::NotHandled` if all modules return `Error::NotHandled` - // * events from first successful deliver call - fn deliver_msg(&self, message: Any, signer: &AccountId) -> Result, Error> { - let mut modules = self.modules.write().unwrap(); - let mut handled = false; - let mut events = vec![]; - - for IdentifiedModule { module, .. } in modules.iter_mut() { - match module.deliver(message.clone(), signer) { - Ok(mut msg_events) => { - events.append(&mut msg_events); - handled = true; - break; - } - Err(Error::NotHandled) => continue, - Err(e) => { - error!("deliver message ({:?}) failed with error: {:?}", message, e); - return Err(e); - } - } - } - if handled { - Ok(events) - } else { - Err(Error::NotHandled) - } - } -} - -impl Application for BaseCoinApp { - fn info(&self, request: RequestInfo) -> ResponseInfo { - let (last_block_height, last_block_app_hash) = { - let state = self.store.read().unwrap(); - (state.current_height() as i64, state.root_hash()) - }; - debug!( - "Got info request. Tendermint version: {}; Block version: {}; P2P version: {}, {:?}, {:?}", - request.version, request.block_version, request.p2p_version, last_block_height, last_block_app_hash - ); - ResponseInfo { - data: "basecoin-rs".to_string(), - version: "0.1.0".to_string(), - app_version: 1, - last_block_height, - last_block_app_hash: last_block_app_hash.into(), - } - } - - fn init_chain(&self, request: RequestInitChain) -> ResponseInitChain { - debug!("Got init chain request."); - - // safety - we panic on errors to prevent chain creation with invalid genesis config - let app_state: Value = serde_json::from_str( - &String::from_utf8(request.app_state_bytes.clone().into()) - .expect("invalid genesis state"), - ) - .expect("genesis state isn't valid JSON"); - let mut modules = self.modules.write().unwrap(); - for IdentifiedModule { module, .. } in modules.iter_mut() { - module.init(app_state.clone()); - } - - info!("App initialized"); - - ResponseInitChain { - consensus_params: request.consensus_params, - validators: vec![], // use validator set proposed by tendermint (ie. in the genesis file) - app_hash: self.store.write().unwrap().root_hash().into(), - } - } - - fn query(&self, request: RequestQuery) -> ResponseQuery { - debug!("Got query request: {:?}", request); - - let path: Option = request.path.try_into().ok(); - let modules = self.modules.read().unwrap(); - let height = Height::from(request.height as u64); - for IdentifiedModule { id, module } in modules.iter() { - match module.query(&request.data, path.as_ref(), height, request.prove) { - // success - implies query was handled by this module, so return response - Ok(result) => { - let store = self.store.read().unwrap(); - let proof_ops = if request.prove { - let proof = store.get_proof(height, &id.clone().into()).unwrap(); - let mut buffer = Vec::new(); - proof.encode(&mut buffer).unwrap(); // safety - cannot fail since buf is a vector - - let mut ops = vec![]; - if let Some(mut proofs) = result.proof { - ops.append(&mut proofs); - } - ops.push(ProofOp { - r#type: "".to_string(), - key: id.to_string().into_bytes(), - data: buffer, - }); - Some(ProofOps { ops }) - } else { - None - }; - debug!("Query result: {:?}", result.data); - return ResponseQuery { - code: 0, - log: "exists".to_string(), - key: request.data, - value: result.data.into(), - proof_ops, - height: store.current_height() as i64, - ..Default::default() - }; - } - // `Error::NotHandled` - implies query isn't known or was intercepted but not - // responded to by this module, so try with next module - Err(Error::NotHandled) => continue, - // Other error - return immediately - Err(e) => return ResponseQuery::from_error(1, format!("query error: {e:?}")), - } - } - ResponseQuery::from_error(1, "query msg not handled") - } - - fn deliver_tx(&self, request: RequestDeliverTx) -> ResponseDeliverTx { - debug!("Got deliverTx request: {request:?}"); - - let tx: Tx = match request.tx.as_ref().try_into() { - Ok(tx) => tx, - Err(err) => { - return ResponseDeliverTx::from_error( - 1, - format!("failed to decode incoming tx bytes: {err}"), - ); - } - }; - - // Extract `AccountId` of first signer - let signer = { - let pubkey = match tx.auth_info.signer_infos.first() { - Some(&SignerInfo { - public_key: Some(SignerPublicKey::Single(pubkey)), - .. - }) => pubkey, - _ => return ResponseDeliverTx::from_error(2, "Empty signers"), - }; - if let Ok(signer) = pubkey.account_id(ACCOUNT_PREFIX) { - signer - } else { - return ResponseDeliverTx::from_error(2, "Invalid signer"); - } - }; - - if tx.body.messages.is_empty() { - return ResponseDeliverTx::from_error(2, "Empty Tx"); - } - - let mut events = vec![]; - for message in tx.body.messages { - let message = Any { - type_url: message.type_url, - value: message.value, - }; - - // try to deliver message to every module - match self.deliver_msg(message, &signer) { - // success - append events and continue with next message - Ok(mut msg_events) => { - events.append(&mut msg_events); - } - // return on first error - - // either an error that occurred during execution of this message OR no module - // could handle this message - Err(e) => { - // reset changes from other messages in this tx - let mut modules = self.modules.write().unwrap(); - for IdentifiedModule { module, .. } in modules.iter_mut() { - module.store_mut().reset(); - } - self.store.write().unwrap().reset(); - return ResponseDeliverTx::from_error( - 2, - format!("deliver failed with error: {e}"), - ); - } - } - } - - ResponseDeliverTx { - log: "success".to_owned(), - events, - ..ResponseDeliverTx::default() - } - } - - fn commit(&self) -> ResponseCommit { - let mut modules = self.modules.write().unwrap(); - for IdentifiedModule { id, module } in modules.iter_mut() { - module - .store_mut() - .commit() - .expect("failed to commit to state"); - let mut state = self.store.write().unwrap(); - state - .set(id.clone().into(), module.store().root_hash()) - .expect("failed to update sub-store commitment"); - } - - let mut state = self.store.write().unwrap(); - let data = state.commit().expect("failed to commit to state"); - info!( - "Committed height {} with hash({})", - state.current_height() - 1, - data.iter().map(|b| format!("{b:02X}")).collect::() - ); - ResponseCommit { - data: data.into(), - retain_height: 0, - } - } - - fn begin_block(&self, request: RequestBeginBlock) -> ResponseBeginBlock { - debug!("Got begin block request."); - - let mut modules = self.modules.write().unwrap(); - let mut events = vec![]; - let header = request.header.unwrap().try_into().unwrap(); - for IdentifiedModule { module, .. } in modules.iter_mut() { - events.extend(module.begin_block(&header)); - } - - ResponseBeginBlock { events } - } -} - -#[tonic::async_trait] -impl HealthService for BaseCoinApp { - async fn abci_query( - &self, - _request: Request, - ) -> Result, Status> { - unimplemented!() - } - - async fn get_node_info( - &self, - _request: Request, - ) -> Result, Status> { - debug!("Got node info request"); - - // TODO(hu55a1n1): generate below info using build script - Ok(Response::new(GetNodeInfoResponse { - default_node_info: Some(DefaultNodeInfo::default()), - application_version: Some(VersionInfo { - name: "basecoin-rs".to_string(), - app_name: "basecoind".to_string(), - version: "0.1.0".to_string(), - git_commit: "209afef7e99ebcb814b25b6738d033aa5e1a932c".to_string(), - build_deps: vec![VersionInfoModule { - path: "github.com/cosmos/cosmos-sdk".to_string(), - version: "v0.43.0".to_string(), - sum: "h1:ps1QWfvaX6VLNcykA7wzfii/5IwBfYgTIik6NOVDq/c=".to_string(), - }], - ..VersionInfo::default() - }), - })) - } - - async fn get_syncing( - &self, - _request: Request, - ) -> Result, Status> { - unimplemented!() - } - - async fn get_latest_block( - &self, - _request: Request, - ) -> Result, Status> { - unimplemented!() - } - - async fn get_block_by_height( - &self, - _request: Request, - ) -> Result, Status> { - unimplemented!() - } - - async fn get_latest_validator_set( - &self, - _request: Request, - ) -> Result, Status> { - unimplemented!() - } - - async fn get_validator_set_by_height( - &self, - _request: Request, - ) -> Result, Status> { - unimplemented!() - } -} - -#[tonic::async_trait] -impl TxService for BaseCoinApp { - async fn simulate( - &self, - request: Request, - ) -> Result, Status> { - // TODO(hu55a1n1): implement tx based simulate - let _: Tx = request - .into_inner() - .tx_bytes - .as_slice() - .try_into() - .map_err(|_| Status::invalid_argument("failed to deserialize tx"))?; - Ok(Response::new(SimulateResponse { - gas_info: None, - result: None, - })) - } - - async fn get_tx( - &self, - _request: Request, - ) -> Result, Status> { - unimplemented!() - } - - async fn broadcast_tx( - &self, - _request: Request, - ) -> Result, Status> { - unimplemented!() - } - - async fn get_txs_event( - &self, - _request: Request, - ) -> Result, Status> { - unimplemented!() - } - - async fn get_block_with_txs( - &self, - _request: Request, - ) -> Result, Status> { - unimplemented!() - } -} - -/// We have to create this type since the compiler doesn't think that -/// `dyn Future> + Send` -/// can be sent across threads... -pub type SendFuture = dyn Future> + Send; - -impl Service for BaseCoinApp -where - S: Default + ProvableStore + Send + 'static, -{ - type Response = AbciResponse; - type Error = BoxError; - type Future = Pin>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: AbciRequest) -> Self::Future { - let response = match req { - AbciRequest::Echo(domain_req) => { - let proto_req: RequestEcho = domain_req.into(); - - let proto_resp = self.echo(proto_req); - - AbciResponse::Echo(proto_resp.try_into().unwrap()) - } - AbciRequest::Flush => AbciResponse::Flush, - AbciRequest::Info(domain_req) => { - let proto_req: RequestInfo = domain_req.into(); - - let proto_resp = self.info(proto_req); - - AbciResponse::Info(proto_resp.try_into().unwrap()) - } - AbciRequest::InitChain(domain_req) => { - let proto_req: RequestInitChain = domain_req.into(); - - let proto_resp = self.init_chain(proto_req); - - AbciResponse::InitChain(proto_resp.try_into().unwrap()) - } - AbciRequest::Query(domain_req) => { - let proto_req: RequestQuery = domain_req.into(); - - let proto_resp = self.query(proto_req); - - AbciResponse::Query(proto_resp.try_into().unwrap()) - } - AbciRequest::BeginBlock(domain_req) => { - let proto_req: RequestBeginBlock = domain_req.into(); - - let proto_resp = self.begin_block(proto_req); - - AbciResponse::BeginBlock(proto_resp.try_into().unwrap()) - } - AbciRequest::CheckTx(domain_req) => { - let proto_req: RequestCheckTx = domain_req.into(); - - let proto_resp = self.check_tx(proto_req); - - AbciResponse::CheckTx(proto_resp.try_into().unwrap()) - } - AbciRequest::DeliverTx(domain_req) => { - let proto_req: RequestDeliverTx = domain_req.into(); - - let proto_resp = self.deliver_tx(proto_req); - - AbciResponse::DeliverTx(proto_resp.try_into().unwrap()) - } - AbciRequest::EndBlock(domain_req) => { - let proto_req: RequestEndBlock = domain_req.into(); - - let proto_resp = self.end_block(proto_req); - - AbciResponse::EndBlock(proto_resp.try_into().unwrap()) - } - AbciRequest::Commit => { - let proto_resp = self.commit(); - - AbciResponse::Commit(proto_resp.try_into().unwrap()) - } - AbciRequest::ListSnapshots => { - let proto_resp = self.list_snapshots(); - - AbciResponse::ListSnapshots(proto_resp.try_into().unwrap()) - } - AbciRequest::OfferSnapshot(domain_req) => { - let proto_req: RequestOfferSnapshot = domain_req.into(); - - let proto_resp = self.offer_snapshot(proto_req); - - AbciResponse::OfferSnapshot(proto_resp.try_into().unwrap()) - } - AbciRequest::LoadSnapshotChunk(domain_req) => { - let proto_req: RequestLoadSnapshotChunk = domain_req.into(); - - let proto_resp = self.load_snapshot_chunk(proto_req); - - AbciResponse::LoadSnapshotChunk(proto_resp.try_into().unwrap()) - } - AbciRequest::ApplySnapshotChunk(domain_req) => { - let proto_req: RequestApplySnapshotChunk = domain_req.into(); - - let proto_resp = self.apply_snapshot_chunk(proto_req); - - AbciResponse::ApplySnapshotChunk(proto_resp.try_into().unwrap()) - } - AbciRequest::PrepareProposal(_) => unimplemented!(), - AbciRequest::ProcessProposal(_) => unimplemented!(), - }; - - Box::pin(future::ready(Ok(response))) - } -} diff --git a/src/app/builder.rs b/src/app/builder.rs new file mode 100644 index 00000000..6b65c8f3 --- /dev/null +++ b/src/app/builder.rs @@ -0,0 +1,115 @@ +use std::sync::{Arc, RwLock}; +use tracing::error; + +use cosmrs::AccountId; +use ibc_proto::google::protobuf::Any; +use tendermint_proto::abci::Event; + +use crate::error::Error; +use crate::helper::Identifier; +use crate::modules::types::IdentifiedModule; +use crate::modules::types::ModuleList; +use crate::modules::types::ModuleStore; + +use crate::modules::Module; + +use crate::store::MainStore; +use crate::store::ProvableStore; +use crate::store::RevertibleStore; +use crate::store::SharedRw; +use crate::store::SharedStore; + +pub struct Builder { + store: MainStore, + modules: SharedRw>, +} + +impl Builder { + /// Constructor. + pub fn new(store: S) -> Self { + Self { + store: SharedStore::new(RevertibleStore::new(store)), + modules: Arc::new(RwLock::new(vec![])), + } + } + + /// Returns a share to the module's store if a module with specified identifier was previously + /// added, otherwise creates a new module store and returns it. + pub fn module_store(&self, prefix: &Identifier) -> SharedStore> { + let modules = self.modules.read().unwrap(); + modules + .iter() + .find(|m| &m.id == prefix) + .map(|IdentifiedModule { module, .. }| module.store().share()) + .unwrap_or_else(|| SharedStore::new(ModuleStore::new(S::default()))) + } + + #[inline] + fn is_unique_id(&self, prefix: &Identifier) -> bool { + !self.modules.read().unwrap().iter().any(|m| &m.id == prefix) + } + + /// Adds a new module. Panics if a module with the specified identifier was previously added. + pub fn add_module( + self, + prefix: Identifier, + module: impl Module> + 'static, + ) -> Self { + assert!(self.is_unique_id(&prefix), "module prefix must be unique"); + self.modules.write().unwrap().push(IdentifiedModule { + id: prefix, + module: Box::new(module), + }); + self + } + + pub fn build(self) -> BaseCoinApp { + BaseCoinApp { + store: self.store, + modules: self.modules, + } + } +} + +/// BaseCoin ABCI application. +/// +/// Can be safely cloned and sent across threads, but not shared. +#[derive(Clone)] +pub struct BaseCoinApp { + pub store: MainStore, + pub modules: SharedRw>, +} + +impl BaseCoinApp { + // try to deliver the message to all registered modules + // if `module.deliver()` returns `Error::NotHandled`, try next module + // Return: + // * other errors immediately OR + // * `Error::NotHandled` if all modules return `Error::NotHandled` + // * events from first successful deliver call + pub fn deliver_msg(&self, message: Any, signer: &AccountId) -> Result, Error> { + let mut modules = self.modules.write().unwrap(); + let mut handled = false; + let mut events = vec![]; + + for IdentifiedModule { module, .. } in modules.iter_mut() { + match module.deliver(message.clone(), signer) { + Ok(mut msg_events) => { + events.append(&mut msg_events); + handled = true; + break; + } + Err(Error::NotHandled) => continue, + Err(e) => { + error!("deliver message ({:?}) failed with error: {:?}", message, e); + return Err(e); + } + } + } + if handled { + Ok(events) + } else { + Err(Error::NotHandled) + } + } +} diff --git a/src/app/interface/mod.rs b/src/app/interface/mod.rs new file mode 100644 index 00000000..a30878e1 --- /dev/null +++ b/src/app/interface/mod.rs @@ -0,0 +1,7 @@ +//! Different interface implementations to interact with the underlying +//! consensus engine. + +pub mod tendermint; + +#[cfg(feature = "tower-abci")] +pub mod tower_abci; diff --git a/src/app/interface/tendermint.rs b/src/app/interface/tendermint.rs new file mode 100644 index 00000000..2bc8d2cb --- /dev/null +++ b/src/app/interface/tendermint.rs @@ -0,0 +1,235 @@ +use prost::Message; +use serde_json::Value; +use std::convert::TryInto; +use tracing::{debug, info}; + +use cosmrs::tx::SignerInfo; +use cosmrs::tx::SignerPublicKey; +use cosmrs::Tx; + +use ibc_proto::google::protobuf::Any; + +use tendermint_abci::Application; +use tendermint_proto::abci::RequestBeginBlock; +use tendermint_proto::abci::RequestDeliverTx; +use tendermint_proto::abci::RequestInfo; +use tendermint_proto::abci::RequestInitChain; +use tendermint_proto::abci::RequestQuery; +use tendermint_proto::abci::ResponseBeginBlock; +use tendermint_proto::abci::ResponseCommit; +use tendermint_proto::abci::ResponseDeliverTx; +use tendermint_proto::abci::ResponseInfo; +use tendermint_proto::abci::ResponseInitChain; +use tendermint_proto::abci::ResponseQuery; +use tendermint_proto::crypto::ProofOp; +use tendermint_proto::crypto::ProofOps; + +use crate::app::BaseCoinApp; +use crate::error::Error; +use crate::helper::macros::ResponseFromErrorExt; +use crate::helper::{Height, Path}; +use crate::modules::{auth::account::ACCOUNT_PREFIX, types::IdentifiedModule}; +use crate::store::{ProvableStore, Store}; + +impl Application for BaseCoinApp { + fn info(&self, request: RequestInfo) -> ResponseInfo { + let (last_block_height, last_block_app_hash) = { + let state = self.store.read().unwrap(); + (state.current_height() as i64, state.root_hash()) + }; + debug!( + "Got info request. Tendermint version: {}; Block version: {}; P2P version: {}, {:?}, {:?}", + request.version, request.block_version, request.p2p_version, last_block_height, last_block_app_hash + ); + ResponseInfo { + data: "basecoin-rs".to_string(), + version: "0.1.0".to_string(), + app_version: 1, + last_block_height, + last_block_app_hash: last_block_app_hash.into(), + } + } + + fn init_chain(&self, request: RequestInitChain) -> ResponseInitChain { + debug!("Got init chain request."); + + // safety - we panic on errors to prevent chain creation with invalid genesis config + let app_state: Value = serde_json::from_str( + &String::from_utf8(request.app_state_bytes.clone().into()) + .expect("invalid genesis state"), + ) + .expect("genesis state isn't valid JSON"); + let mut modules = self.modules.write().unwrap(); + for IdentifiedModule { module, .. } in modules.iter_mut() { + module.init(app_state.clone()); + } + + info!("App initialized"); + + ResponseInitChain { + consensus_params: request.consensus_params, + validators: vec![], // use validator set proposed by tendermint (ie. in the genesis file) + app_hash: self.store.write().unwrap().root_hash().into(), + } + } + + fn query(&self, request: RequestQuery) -> ResponseQuery { + debug!("Got query request: {:?}", request); + + let path: Option = request.path.try_into().ok(); + let modules = self.modules.read().unwrap(); + let height = Height::from(request.height as u64); + for IdentifiedModule { id, module } in modules.iter() { + match module.query(&request.data, path.as_ref(), height, request.prove) { + // success - implies query was handled by this module, so return response + Ok(result) => { + let store = self.store.read().unwrap(); + let proof_ops = if request.prove { + let proof = store.get_proof(height, &id.clone().into()).unwrap(); + let mut buffer = Vec::new(); + proof.encode(&mut buffer).unwrap(); // safety - cannot fail since buf is a vector + + let mut ops = vec![]; + if let Some(mut proofs) = result.proof { + ops.append(&mut proofs); + } + ops.push(ProofOp { + r#type: "".to_string(), + key: id.to_string().into_bytes(), + data: buffer, + }); + Some(ProofOps { ops }) + } else { + None + }; + + return ResponseQuery { + code: 0, + log: "exists".to_string(), + key: request.data, + value: result.data.into(), + proof_ops, + height: store.current_height() as i64, + ..Default::default() + }; + } + // `Error::NotHandled` - implies query isn't known or was intercepted but not + // responded to by this module, so try with next module + Err(Error::NotHandled) => continue, + // Other error - return immediately + Err(e) => return ResponseQuery::from_error(1, format!("query error: {e:?}")), + } + } + ResponseQuery::from_error(1, "query msg not handled") + } + + fn deliver_tx(&self, request: RequestDeliverTx) -> ResponseDeliverTx { + debug!("Got deliverTx request: {request:?}"); + + let tx: Tx = match request.tx.as_ref().try_into() { + Ok(tx) => tx, + Err(err) => { + return ResponseDeliverTx::from_error( + 1, + format!("failed to decode incoming tx bytes: {err}"), + ); + } + }; + + // Extract `AccountId` of first signer + let signer = { + let pubkey = match tx.auth_info.signer_infos.first() { + Some(&SignerInfo { + public_key: Some(SignerPublicKey::Single(pubkey)), + .. + }) => pubkey, + _ => return ResponseDeliverTx::from_error(2, "Empty signers"), + }; + if let Ok(signer) = pubkey.account_id(ACCOUNT_PREFIX) { + signer + } else { + return ResponseDeliverTx::from_error(2, "Invalid signer"); + } + }; + + if tx.body.messages.is_empty() { + return ResponseDeliverTx::from_error(2, "Empty Tx"); + } + + let mut events = vec![]; + for message in tx.body.messages { + let message = Any { + type_url: message.type_url, + value: message.value, + }; + + // try to deliver message to every module + match self.deliver_msg(message, &signer) { + // success - append events and continue with next message + Ok(mut msg_events) => { + events.append(&mut msg_events); + } + // return on first error - + // either an error that occurred during execution of this message OR no module + // could handle this message + Err(e) => { + // reset changes from other messages in this tx + let mut modules = self.modules.write().unwrap(); + for IdentifiedModule { module, .. } in modules.iter_mut() { + module.store_mut().reset(); + } + self.store.write().unwrap().reset(); + return ResponseDeliverTx::from_error( + 2, + format!("deliver failed with error: {e}"), + ); + } + } + } + + ResponseDeliverTx { + log: "success".to_owned(), + events, + ..ResponseDeliverTx::default() + } + } + + fn commit(&self) -> ResponseCommit { + let mut modules = self.modules.write().unwrap(); + for IdentifiedModule { id, module } in modules.iter_mut() { + module + .store_mut() + .commit() + .expect("failed to commit to state"); + let mut state = self.store.write().unwrap(); + state + .set(id.clone().into(), module.store().root_hash()) + .expect("failed to update sub-store commitment"); + } + + let mut state = self.store.write().unwrap(); + let data = state.commit().expect("failed to commit to state"); + info!( + "Committed height {} with hash({})", + state.current_height() - 1, + data.iter().map(|b| format!("{b:02X}")).collect::() + ); + ResponseCommit { + data: data.into(), + retain_height: 0, + } + } + + fn begin_block(&self, request: RequestBeginBlock) -> ResponseBeginBlock { + debug!("Got begin block request."); + + let mut modules = self.modules.write().unwrap(); + let mut events = vec![]; + let header = request.header.unwrap().try_into().unwrap(); + for IdentifiedModule { module, .. } in modules.iter_mut() { + events.extend(module.begin_block(&header)); + } + + ResponseBeginBlock { events } + } +} diff --git a/src/app/interface/tower_abci.rs b/src/app/interface/tower_abci.rs new file mode 100644 index 00000000..7bd946a3 --- /dev/null +++ b/src/app/interface/tower_abci.rs @@ -0,0 +1,148 @@ +use std::future::{self, Future}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use tendermint::v0_37::abci::response::Response as AbciResponse; +use tendermint::v0_37::abci::Request as AbciRequest; +use tendermint_abci::Application; + +use tendermint_proto::abci::RequestApplySnapshotChunk; +use tendermint_proto::abci::RequestBeginBlock; +use tendermint_proto::abci::RequestCheckTx; +use tendermint_proto::abci::RequestDeliverTx; +use tendermint_proto::abci::RequestEcho; +use tendermint_proto::abci::RequestEndBlock; +use tendermint_proto::abci::RequestInfo; +use tendermint_proto::abci::RequestInitChain; +use tendermint_proto::abci::RequestLoadSnapshotChunk; +use tendermint_proto::abci::RequestOfferSnapshot; +use tendermint_proto::abci::RequestQuery; + +use tower::Service; +use tower_abci::BoxError; + +use crate::app::BaseCoinApp; +use crate::store::ProvableStore; + +/// We have to create this type since the compiler doesn't think that +/// `dyn Future> + Send` +/// can be sent across threads... +pub type SendFuture = dyn Future> + Send; + +impl Service for BaseCoinApp +where + S: Default + ProvableStore + Send + 'static, +{ + type Response = AbciResponse; + type Error = BoxError; + type Future = Pin>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: AbciRequest) -> Self::Future { + let response = match req { + AbciRequest::Echo(domain_req) => { + let proto_req: RequestEcho = domain_req.into(); + + let proto_resp = self.echo(proto_req); + + AbciResponse::Echo(proto_resp.try_into().unwrap()) + } + AbciRequest::Flush => AbciResponse::Flush, + AbciRequest::Info(domain_req) => { + let proto_req: RequestInfo = domain_req.into(); + + let proto_resp = self.info(proto_req); + + AbciResponse::Info(proto_resp.try_into().unwrap()) + } + AbciRequest::InitChain(domain_req) => { + let proto_req: RequestInitChain = domain_req.into(); + + let proto_resp = self.init_chain(proto_req); + + AbciResponse::InitChain(proto_resp.try_into().unwrap()) + } + AbciRequest::Query(domain_req) => { + let proto_req: RequestQuery = domain_req.into(); + + let proto_resp = self.query(proto_req); + + AbciResponse::Query(proto_resp.try_into().unwrap()) + } + AbciRequest::BeginBlock(domain_req) => { + let proto_req: RequestBeginBlock = domain_req.into(); + + let proto_resp = self.begin_block(proto_req); + + AbciResponse::BeginBlock(proto_resp.try_into().unwrap()) + } + AbciRequest::CheckTx(domain_req) => { + let proto_req: RequestCheckTx = domain_req.into(); + + let proto_resp = self.check_tx(proto_req); + + AbciResponse::CheckTx(proto_resp.try_into().unwrap()) + } + AbciRequest::DeliverTx(domain_req) => { + let proto_req: RequestDeliverTx = domain_req.into(); + + let proto_resp = self.deliver_tx(proto_req); + + AbciResponse::DeliverTx(proto_resp.try_into().unwrap()) + } + AbciRequest::EndBlock(domain_req) => { + let proto_req: RequestEndBlock = domain_req.into(); + + let proto_resp = self.end_block(proto_req); + + AbciResponse::EndBlock(proto_resp.try_into().unwrap()) + } + AbciRequest::Commit => { + let proto_resp = self.commit(); + + AbciResponse::Commit(proto_resp.try_into().unwrap()) + } + AbciRequest::ListSnapshots => { + let proto_resp = self.list_snapshots(); + + AbciResponse::ListSnapshots(proto_resp.try_into().unwrap()) + } + AbciRequest::OfferSnapshot(domain_req) => { + let proto_req: RequestOfferSnapshot = domain_req.into(); + + let proto_resp = self.offer_snapshot(proto_req); + + AbciResponse::OfferSnapshot(proto_resp.try_into().unwrap()) + } + AbciRequest::LoadSnapshotChunk(domain_req) => { + let proto_req: RequestLoadSnapshotChunk = domain_req.into(); + + let proto_resp = self.load_snapshot_chunk(proto_req); + + AbciResponse::LoadSnapshotChunk(proto_resp.try_into().unwrap()) + } + AbciRequest::ApplySnapshotChunk(domain_req) => { + let proto_req: RequestApplySnapshotChunk = domain_req.into(); + + let proto_resp = self.apply_snapshot_chunk(proto_req); + + AbciResponse::ApplySnapshotChunk(proto_resp.try_into().unwrap()) + } + AbciRequest::PrepareProposal(domain_req) => { + let proto_resp = self.prepare_proposal(domain_req.into()); + + AbciResponse::PrepareProposal(proto_resp.try_into().unwrap()) + } + AbciRequest::ProcessProposal(domain_req) => { + let proto_resp = self.process_proposal(domain_req.into()); + + AbciResponse::ProcessProposal(proto_resp.try_into().unwrap()) + } + }; + + Box::pin(future::ready(Ok(response))) + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs new file mode 100644 index 00000000..fdc8ffaa --- /dev/null +++ b/src/app/mod.rs @@ -0,0 +1,10 @@ +pub mod interface; +pub mod service; + +mod builder; +pub use builder::{BaseCoinApp, Builder}; + +mod runner; +pub use runner::default_app_runner; + +pub(crate) const CHAIN_REVISION_NUMBER: u64 = 0; diff --git a/src/app/runner.rs b/src/app/runner.rs new file mode 100644 index 00000000..2f29a1d2 --- /dev/null +++ b/src/app/runner.rs @@ -0,0 +1,122 @@ +use ibc_proto::cosmos::base::tendermint::v1beta1::service_server::ServiceServer as HealthServer; +use ibc_proto::cosmos::tx::v1beta1::service_server::ServiceServer as TxServer; +use tracing::info; + +use super::Builder; +use crate::config::ServerConfig; +use crate::modules::prefix; +use crate::modules::Auth; +use crate::modules::Bank; +use crate::modules::Governance; +use crate::modules::Ibc; +use crate::modules::Identifiable; +use crate::modules::Staking; +use crate::modules::Upgrade; +use crate::store::memory::InMemoryStore; + +#[cfg(not(feature = "tower-abci"))] +use tendermint_abci::ServerBuilder; + +#[cfg(feature = "tower-abci")] +use tower_abci::split; + +pub async fn default_app_runner(server_cfg: ServerConfig) { + // instantiate the application with a KV store implementation of choice + let app_builder = Builder::new(InMemoryStore::default()); + + // instantiate modules and setup inter-module communication (if required) + let auth = Auth::new(app_builder.module_store(&prefix::Auth {}.identifier())); + let bank = Bank::new( + app_builder.module_store(&prefix::Bank {}.identifier()), + auth.account_reader().clone(), + auth.account_keeper().clone(), + ); + let staking = Staking::new(app_builder.module_store(&prefix::Staking {}.identifier())); + let ibc = Ibc::new( + app_builder.module_store(&prefix::Ibc {}.identifier()), + bank.bank_keeper().clone(), + ); + let upgrade = Upgrade::new(app_builder.module_store(&prefix::Upgrade {}.identifier())); + let governance = Governance::new( + app_builder.module_store(&prefix::Governance {}.identifier()), + upgrade.clone(), + ); + + // instantiate gRPC services for each module + let auth_service = auth.service(); + let bank_service = bank.service(); + let ibc_client_service = ibc.client_service(); + let ibc_conn_service = ibc.connection_service(); + let ibc_channel_service = ibc.channel_service(); + let governance_service = governance.service(); + let staking_service = staking.service(); + let upgrade_service = upgrade.service(); + + // register modules with the app + let app = app_builder + .add_module(prefix::Auth {}.identifier(), auth.clone()) + .add_module(prefix::Bank {}.identifier(), bank.clone()) + .add_module(prefix::Ibc {}.identifier(), ibc) + .add_module(prefix::Governance {}.identifier(), governance.clone()) + .add_module(prefix::Upgrade {}.identifier(), upgrade.clone()) + .build(); + + #[cfg(not(feature = "tower-abci"))] + { + info!("Starting Tendermint ABCI server"); + + // run the blocking ABCI server on a separate thread + let server = ServerBuilder::new(server_cfg.read_buf_size) + .bind( + format!("{}:{}", server_cfg.host, server_cfg.port), + app.clone(), + ) + .unwrap(); + + std::thread::spawn(move || { + server.listen().unwrap(); + }); + } + + #[cfg(feature = "tower-abci")] + { + info!("Starting tower ABCI server"); + + let app_split = app.clone(); + let (consensus, mempool, snapshot, info) = split::service(app_split, 10); + + let server = tower_abci::v037::Server::builder() + .consensus(consensus) + .mempool(mempool) + .info(info) + .snapshot(snapshot) + .finish() + .expect("tower_abci::Server building failed"); + + // run the blocking ABCI server on a separate thread + let server_listen_addr = format!("{}:{}", server_cfg.host, server_cfg.port); + tokio::task::spawn(async move { + server.listen(server_listen_addr).await.unwrap(); + }); + } + + // run the gRPC server + let grpc_server = tonic::transport::Server::builder() + .add_service(HealthServer::new(app.clone())) + .add_service(TxServer::new(app.clone())) + .add_service(ibc_client_service) + .add_service(ibc_conn_service) + .add_service(ibc_channel_service) + .add_service(auth_service) + .add_service(bank_service) + .add_service(governance_service) + .add_service(staking_service) + .add_service(upgrade_service) + .serve( + format!("{}:{}", server_cfg.host, server_cfg.grpc_port) + .parse() + .unwrap(), + ); + + grpc_server.await.unwrap() +} diff --git a/src/app/service.rs b/src/app/service.rs new file mode 100644 index 00000000..43eeae5e --- /dev/null +++ b/src/app/service.rs @@ -0,0 +1,156 @@ +use ibc_proto::cosmos::base::tendermint::v1beta1::GetNodeInfoResponse; +use std::convert::TryInto; +use tracing::debug; + +use cosmrs::Tx; +use tendermint_proto::p2p::DefaultNodeInfo; +use tonic::{Request, Response, Status}; + +use ibc_proto::cosmos::base::tendermint::v1beta1::service_server::Service as HealthService; +use ibc_proto::cosmos::base::tendermint::v1beta1::AbciQueryRequest; +use ibc_proto::cosmos::base::tendermint::v1beta1::AbciQueryResponse; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetBlockByHeightRequest; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetBlockByHeightResponse; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetLatestBlockRequest; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetLatestBlockResponse; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetLatestValidatorSetRequest; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetLatestValidatorSetResponse; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetNodeInfoRequest; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetSyncingRequest; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetSyncingResponse; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetValidatorSetByHeightRequest; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetValidatorSetByHeightResponse; +use ibc_proto::cosmos::base::tendermint::v1beta1::Module as VersionInfoModule; +use ibc_proto::cosmos::base::tendermint::v1beta1::VersionInfo; + +use ibc_proto::cosmos::tx::v1beta1::service_server::Service as TxService; +use ibc_proto::cosmos::tx::v1beta1::BroadcastTxRequest; +use ibc_proto::cosmos::tx::v1beta1::BroadcastTxResponse; +use ibc_proto::cosmos::tx::v1beta1::GetBlockWithTxsRequest; +use ibc_proto::cosmos::tx::v1beta1::GetBlockWithTxsResponse; +use ibc_proto::cosmos::tx::v1beta1::GetTxRequest; +use ibc_proto::cosmos::tx::v1beta1::GetTxResponse; +use ibc_proto::cosmos::tx::v1beta1::GetTxsEventRequest; +use ibc_proto::cosmos::tx::v1beta1::GetTxsEventResponse; +use ibc_proto::cosmos::tx::v1beta1::SimulateRequest; +use ibc_proto::cosmos::tx::v1beta1::SimulateResponse; + +use super::builder::BaseCoinApp; +use crate::store::ProvableStore; + +#[tonic::async_trait] +impl HealthService for BaseCoinApp { + async fn abci_query( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn get_node_info( + &self, + _request: Request, + ) -> Result, Status> { + debug!("Got node info request"); + + // TODO(hu55a1n1): generate below info using build script + Ok(Response::new(GetNodeInfoResponse { + default_node_info: Some(DefaultNodeInfo::default()), + application_version: Some(VersionInfo { + name: "basecoin-rs".to_string(), + app_name: "basecoind".to_string(), + version: "0.1.0".to_string(), + git_commit: "209afef7e99ebcb814b25b6738d033aa5e1a932c".to_string(), + build_deps: vec![VersionInfoModule { + path: "github.com/cosmos/cosmos-sdk".to_string(), + version: "v0.47.0".to_string(), + sum: "h1:ps1QWfvaX6VLNcykA7wzfii/5IwBfYgTIik6NOVDq/c=".to_string(), + }], + ..VersionInfo::default() + }), + })) + } + + async fn get_syncing( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn get_latest_block( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn get_block_by_height( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn get_latest_validator_set( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn get_validator_set_by_height( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } +} + +#[tonic::async_trait] +impl TxService for BaseCoinApp { + async fn simulate( + &self, + request: Request, + ) -> Result, Status> { + // TODO(hu55a1n1): implement tx based simulate + let _: Tx = request + .into_inner() + .tx_bytes + .as_slice() + .try_into() + .map_err(|_| Status::invalid_argument("failed to deserialize tx"))?; + Ok(Response::new(SimulateResponse { + gas_info: None, + result: None, + })) + } + + async fn get_tx( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn broadcast_tx( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn get_txs_event( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn get_block_with_txs( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } +} diff --git a/src/bin/basecoin-tower/main.rs b/src/bin/basecoin-tower/main.rs deleted file mode 100644 index 8be7dc72..00000000 --- a/src/bin/basecoin-tower/main.rs +++ /dev/null @@ -1,103 +0,0 @@ -use basecoin::{ - app::Builder, - cli::option::Opt, - modules::{prefix, Auth, Bank, Governance, Ibc, Identifiable, Staking, Upgrade}, - store::InMemoryStore, -}; -use ibc_proto::cosmos::{ - base::tendermint::v1beta1::service_server::ServiceServer as HealthServer, - tx::v1beta1::service_server::ServiceServer as TxServer, -}; - -use structopt::StructOpt; -use tower_abci::split; -use tracing_subscriber::filter::LevelFilter; - -#[tokio::main] -async fn main() { - let opt: Opt = Opt::from_args(); - let log_level = if opt.quiet { - LevelFilter::OFF - } else if opt.verbose { - LevelFilter::TRACE - } else { - LevelFilter::INFO - }; - tracing_subscriber::fmt().with_max_level(log_level).init(); - tracing::info!("Starting app and waiting for Tendermint to connect..."); - - let app_builder = Builder::new(InMemoryStore::default()); - - // instantiate modules and setup inter-module communication (if required) - let auth = Auth::new(app_builder.module_store(&prefix::Auth {}.identifier())); - let bank = Bank::new( - app_builder.module_store(&prefix::Bank {}.identifier()), - auth.account_reader().clone(), - auth.account_keeper().clone(), - ); - let staking = Staking::new(app_builder.module_store(&prefix::Staking {}.identifier())); - - let ibc = Ibc::new( - app_builder.module_store(&prefix::Ibc {}.identifier()), - bank.bank_keeper().clone(), - ); - - let upgrade = Upgrade::new(app_builder.module_store(&prefix::Upgrade {}.identifier())); - - let governance = Governance::new( - app_builder.module_store(&prefix::Governance {}.identifier()), - upgrade.clone(), - ); - - // instantiate gRPC services for each module - let auth_service = auth.service(); - let bank_service = bank.service(); - let ibc_client_service = ibc.client_service(); - let ibc_conn_service = ibc.connection_service(); - let ibc_channel_service = ibc.channel_service(); - let governance_service = governance.service(); - let staking_service = staking.service(); - let upgrade_service = upgrade.service(); - - // register modules with the app - let app = app_builder - .add_module(prefix::Auth {}.identifier(), auth) - .add_module(prefix::Bank {}.identifier(), bank) - .add_module(prefix::Ibc {}.identifier(), ibc) - // .add_module(prefix::Governance {}.identifier(), governance) - // .add_module(prefix::Upgrade {}.identifier(), upgrade) - .build(); - - let app_split = app.clone(); - let (consensus, mempool, snapshot, info) = split::service(app_split, 10); - - let server = tower_abci::v037::Server::builder() - .consensus(consensus) - .mempool(mempool) - .info(info) - .snapshot(snapshot) - .finish() - .expect("tower_abci::Server building failed"); - - // run the blocking ABCI server on a separate thread - let server_listen_addr = format!("{}:{}", opt.host, opt.port); - tokio::task::spawn(async move { - server.listen(server_listen_addr).await.unwrap(); - }); - - // run the gRPC server - let grpc_server = tonic::transport::Server::builder() - .add_service(HealthServer::new(app.clone())) - .add_service(TxServer::new(app)) - .add_service(ibc_client_service) - .add_service(ibc_conn_service) - .add_service(ibc_channel_service) - .add_service(auth_service) - .add_service(bank_service) - .add_service(governance_service) - .add_service(staking_service) - .add_service(upgrade_service) - .serve(format!("{}:{}", opt.host, opt.grpc_port).parse().unwrap()); - - grpc_server.await.unwrap(); -} diff --git a/src/bin/basecoin/main.rs b/src/bin/basecoin/main.rs index e4f86e98..68694946 100644 --- a/src/bin/basecoin/main.rs +++ b/src/bin/basecoin/main.rs @@ -4,100 +4,42 @@ #![forbid(unsafe_code)] use basecoin::{ - app::Builder, - cli::option::Opt, - modules::{prefix, Governance, Identifiable, Upgrade}, - modules::{Auth, Bank, Ibc, Staking}, - store::memory::InMemoryStore, + app::default_app_runner, + cli::command::{BasecoinCli, Commands, QueryCmd, UpgradeCmd}, + config::load_config, + modules::query_upgrade_plan, }; -use ibc_proto::cosmos::{ - base::tendermint::v1beta1::service_server::ServiceServer as HealthServer, - tx::v1beta1::service_server::ServiceServer as TxServer, -}; -use structopt::StructOpt; -use tendermint_abci::ServerBuilder; -use tokio::runtime::Runtime; -use tonic::transport::Server; -use tracing_subscriber::filter::LevelFilter; -fn main() { - let opt: Opt = Opt::from_args(); - let log_level = if opt.quiet { +use clap::Parser; +use tracing::metadata::LevelFilter; + +#[tokio::main] +async fn main() { + let cli = BasecoinCli::parse(); + let cfg = load_config(cli.config.clone()).unwrap(); + + let log_level = if cli.quiet { LevelFilter::OFF - } else if opt.verbose { + } else if cli.verbose { LevelFilter::TRACE } else { - LevelFilter::INFO + cfg.global.log_level.clone().into() }; - tracing_subscriber::fmt().with_max_level(log_level).init(); - tracing::info!("Starting app and waiting for Tendermint to connect..."); - - // instantiate the application with a KV store implementation of choice - let app_builder = Builder::new(InMemoryStore::default()); - - // instantiate modules and setup inter-module communication (if required) - let auth = Auth::new(app_builder.module_store(&prefix::Auth {}.identifier())); - let bank = Bank::new( - app_builder.module_store(&prefix::Bank {}.identifier()), - auth.account_reader().clone(), - auth.account_keeper().clone(), - ); - - let staking = Staking::new(app_builder.module_store(&prefix::Staking {}.identifier())); - let ibc = Ibc::new( - app_builder.module_store(&prefix::Ibc {}.identifier()), - bank.bank_keeper().clone(), - ); - - let upgrade = Upgrade::new(app_builder.module_store(&prefix::Upgrade {}.identifier())); - - let governance = Governance::new( - app_builder.module_store(&prefix::Governance {}.identifier()), - upgrade.clone(), - ); - - // instantiate gRPC services for each module - let auth_service = auth.service(); - let bank_service = bank.service(); - let ibc_client_service = ibc.client_service(); - let ibc_conn_service = ibc.connection_service(); - let ibc_channel_service = ibc.channel_service(); - let governance_service = governance.service(); - let staking_service = staking.service(); - let upgrade_service = upgrade.service(); - - // register modules with the app - let app = app_builder - .add_module(prefix::Auth {}.identifier(), auth) - .add_module(prefix::Bank {}.identifier(), bank) - .add_module(prefix::Ibc {}.identifier(), ibc) - .add_module(prefix::Governance {}.identifier(), governance) - .add_module(prefix::Upgrade {}.identifier(), upgrade) - .build(); - - // run the blocking ABCI server on a separate thread - let server = ServerBuilder::new(opt.read_buf_size) - .bind(format!("{}:{}", opt.host, opt.port), app.clone()) - .unwrap(); - std::thread::spawn(move || { - server.listen().unwrap(); - }); + tracing_subscriber::fmt().with_max_level(log_level).init(); - // run the gRPC server - let grpc_server = Server::builder() - .add_service(HealthServer::new(app.clone())) - .add_service(TxServer::new(app)) - .add_service(ibc_client_service) - .add_service(ibc_conn_service) - .add_service(ibc_channel_service) - .add_service(auth_service) - .add_service(bank_service) - .add_service(governance_service) - .add_service(staking_service) - .add_service(upgrade_service) - .serve(format!("{}:{}", opt.host, opt.grpc_port).parse().unwrap()); - Runtime::new() - .unwrap() - .block_on(async { grpc_server.await.unwrap() }); + match &cli.command { + Commands::Start => { + tracing::info!("Starting app and waiting for CometBFT to connect..."); + default_app_runner(cfg.server).await + } + Commands::Query(q) => { + let query_res = match q { + QueryCmd::Upgrade(u) => match u { + UpgradeCmd::Plan => query_upgrade_plan(cfg.cometbft).await.unwrap(), + }, + }; + println!("{:?}", query_res); + } + }; } diff --git a/src/cli/command.rs b/src/cli/command.rs new file mode 100644 index 00000000..7d28fb22 --- /dev/null +++ b/src/cli/command.rs @@ -0,0 +1,48 @@ +use std::path::PathBuf; + +use clap::{command, Parser}; + +#[derive(Clone, Debug, Parser)] +#[command(author, version, about, long_about = None)] +pub struct BasecoinCli { + /// The subcommand to run. + #[command(subcommand)] + pub command: Commands, + + /// The path to the configuration file. + #[arg( + long, + global = true, + value_name = "FILE", + default_value = "config.toml" + )] + pub config: PathBuf, + + /// Increase output logging verbosity to DEBUG level. + #[arg(long, global = true)] + pub verbose: bool, + + /// Suppress all output logging (overrides --verbose). + #[arg(long, global = true)] + pub quiet: bool, +} + +#[derive(Clone, Debug, Parser)] +pub enum Commands { + Start, + #[command(subcommand)] + Query(QueryCmd), +} + +#[derive(Clone, Debug, Parser)] +#[command(about = "Query a state of Basecoin application from the store")] +pub enum QueryCmd { + #[command(subcommand)] + Upgrade(UpgradeCmd), +} + +#[derive(Clone, Debug, Parser)] +#[command(about = "Query commands for the upgrade module")] +pub enum UpgradeCmd { + Plan, +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index ba0e3833..9fe79612 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1 +1 @@ -pub mod option; +pub mod command; diff --git a/src/cli/option.rs b/src/cli/option.rs deleted file mode 100644 index 62527bb0..00000000 --- a/src/cli/option.rs +++ /dev/null @@ -1,29 +0,0 @@ -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct Opt { - /// Bind the TCP server to this host. - #[structopt(short, long, default_value = "127.0.0.1")] - pub host: String, - - /// Bind the TCP server to this port. - #[structopt(short, long, default_value = "26358")] - pub port: u16, - - /// Bind the gRPC server to this port. - #[structopt(short, long, default_value = "9093")] - pub grpc_port: u16, - - /// The default server read buffer size, in bytes, for each incoming client - /// connection. - #[structopt(short, long, default_value = "1048576")] - pub read_buf_size: usize, - - /// Increase output logging verbosity to DEBUG level. - #[structopt(short, long)] - pub verbose: bool, - - /// Suppress all output logging (overrides --verbose). - #[structopt(short, long)] - pub quiet: bool, -} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..a8d499a9 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,65 @@ +use super::error::Error; +use serde_derive::{Deserialize, Serialize}; +pub use std::path::Path; +use tendermint_rpc::Url; +use tracing_subscriber::filter::LevelFilter; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Config { + pub global: GlobalConfig, + pub server: ServerConfig, + pub cometbft: CometbftConfig, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct GlobalConfig { + pub log_level: LogLevel, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub enum LogLevel { + Trace, + Debug, + Info, + Warn, + Error, +} + +impl From for LevelFilter { + fn from(level: LogLevel) -> Self { + match level { + LogLevel::Trace => LevelFilter::TRACE, + LogLevel::Debug => LevelFilter::DEBUG, + LogLevel::Info => LevelFilter::INFO, + LogLevel::Warn => LevelFilter::WARN, + LogLevel::Error => LevelFilter::ERROR, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ServerConfig { + pub host: String, + pub port: u16, + pub grpc_port: u16, + pub read_buf_size: usize, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CometbftConfig { + pub rpc_addr: Url, + pub grpc_addr: Url, +} + +/// Attempt to load and parse the TOML config file as a `Config`. +pub fn load_config(path: impl AsRef) -> Result { + let config_toml = std::fs::read_to_string(&path).map_err(|e| Error::Custom { + reason: e.to_string(), + })?; + + let config = toml::from_str::(&config_toml[..]).map_err(|e| Error::Custom { + reason: e.to_string(), + })?; + + Ok(config) +} diff --git a/src/lib.rs b/src/lib.rs index 575a8084..38ba728b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ #![forbid(unsafe_code)] pub mod app; pub mod cli; +pub mod config; pub mod error; mod helper; pub mod modules; diff --git a/src/modules/bank/impls.rs b/src/modules/bank/impls.rs index 34c23e74..c89c9965 100644 --- a/src/modules/bank/impls.rs +++ b/src/modules/bank/impls.rs @@ -189,6 +189,7 @@ impl BankKeeper for BankBalanceKeeper { } /// The bank module +#[derive(Clone)] pub struct Bank { /// Handle to store instance /// The module is guaranteed exclusive access to all paths in the store key-space. diff --git a/src/modules/ibc/impls.rs b/src/modules/ibc/impls.rs index 55cd5a82..e9eac44f 100644 --- a/src/modules/ibc/impls.rs +++ b/src/modules/ibc/impls.rs @@ -261,11 +261,12 @@ where let path: Path = String::from_utf8(data.to_vec()) .map_err(|_| AppError::Custom { - reason: "Invalid path".to_string(), + reason: "Invalid domain path".to_string(), })? .try_into()?; - let _ = IbcPath::try_from(path.clone()) - .map_err(|_| ContextError::ClientError(ClientError::ImplementationSpecific))?; + let _ = IbcPath::try_from(path.clone()).map_err(|_| AppError::Custom { + reason: "Invalid IBC path".to_string(), + })?; debug!( "Querying for path ({}) at height {:?}", @@ -367,7 +368,9 @@ where let client_state = self .client_state_store .get(Height::Pending, &ClientStatePath(client_id.clone())) - .ok_or(ClientError::ImplementationSpecific) + .ok_or(ClientError::ClientStateNotFound { + client_id: client_id.clone(), + }) .map_err(ContextError::from)?; Ok(Box::new(client_state)) } @@ -497,7 +500,9 @@ where fn connection_end(&self, conn_id: &ConnectionId) -> Result { self.connection_end_store .get(Height::Pending, &ConnectionPath::new(conn_id)) - .ok_or(ConnectionError::Client(ClientError::ImplementationSpecific)) + .ok_or(ConnectionError::ConnectionNotFound { + connection_id: conn_id.clone(), + }) .map_err(ContextError::from) } @@ -685,11 +690,15 @@ where let tm_client_state = client_state .as_any() .downcast_ref::() - .ok_or(ClientError::ImplementationSpecific)?; + .ok_or(ClientError::Other { + description: "Client state type mismatch".to_string(), + })?; self.client_state_store .set(client_state_path, tm_client_state.clone()) .map(|_| ()) - .map_err(|_| ClientError::ImplementationSpecific)?; + .map_err(|_| ClientError::Other { + description: "Client state store error".to_string(), + })?; Ok(()) } @@ -702,10 +711,14 @@ where let tm_consensus_state = consensus_state .as_any() .downcast_ref::() - .ok_or(ClientError::ImplementationSpecific)?; + .ok_or(ClientError::Other { + description: "Consensus state type mismatch".to_string(), + })?; self.consensus_state_store .set(consensus_state_path, tm_consensus_state.clone()) - .map_err(|_| ClientError::ImplementationSpecific)?; + .map_err(|_| ClientError::Other { + description: "Consensus state store error".to_string(), + })?; Ok(()) } @@ -752,7 +765,9 @@ where ) -> Result<(), ContextError> { self.connection_end_store .set(connection_path.clone(), connection_end) - .map_err(|_| ConnectionError::Client(ClientError::ImplementationSpecific))?; + .map_err(|_| ConnectionError::Other { + description: "Connection end store error".to_string(), + })?; Ok(()) } @@ -769,7 +784,9 @@ where conn_ids.push(conn_id); self.connection_ids_store .set(client_connection_path.clone(), conn_ids) - .map_err(|_| ConnectionError::Client(ClientError::ImplementationSpecific))?; + .map_err(|_| ConnectionError::Other { + description: "Connection ids store error".to_string(), + })?; Ok(()) } @@ -835,7 +852,9 @@ where ) -> Result<(), ContextError> { self.channel_end_store .set(channel_end_path.clone(), channel_end) - .map_err(|_| ClientError::ImplementationSpecific)?; + .map_err(|_| ChannelError::Other { + description: "Channel end store error".to_string(), + })?; Ok(()) } diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 3d1ad667..4bd9dfbc 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -16,3 +16,4 @@ pub use bank::impls::Bank; pub use gov::impls::Governance; pub use staking::impls::Staking; pub use upgrade::impls::Upgrade; +pub use upgrade::query::*; diff --git a/src/modules/upgrade/impls.rs b/src/modules/upgrade/impls.rs index 2954cdb5..c95d3d1d 100644 --- a/src/modules/upgrade/impls.rs +++ b/src/modules/upgrade/impls.rs @@ -27,7 +27,7 @@ use super::path::UpgradePlanPath; use super::service::UpgradeService; use crate::error::Error as AppError; use crate::helper::{Height, Path, QueryResult}; -use crate::modules::Module; +use crate::modules::{Module, UPGRADE_PLAN_QUERY_PATH}; use crate::store::{ProtobufStore, ProvableStore, SharedStore, Store, TypedStore}; #[derive(Clone)] @@ -117,17 +117,19 @@ where return Ok(QueryResult { data, proof }); } - if path.to_string() == "/cosmos.upgrade.v1beta1.Query/CurrentPlan" { - let data = self - .store - .get( - Height::Pending, - &Path::from(UpgradePlanPath::sdk_pending_path()), - ) + if path.to_string() == UPGRADE_PLAN_QUERY_PATH { + let plan: Any = self + .upgrade_plan + .get(Height::Pending, &UpgradePlanPath::sdk_pending_path()) .ok_or(AppError::Custom { reason: "Data not found".to_string(), - })?; - return Ok(QueryResult { data, proof: None }); + })? + .into(); + + return Ok(QueryResult { + data: plan.value, + proof: None, + }); } Err(AppError::NotHandled) diff --git a/src/modules/upgrade/mod.rs b/src/modules/upgrade/mod.rs index 416c9333..f9ef495e 100644 --- a/src/modules/upgrade/mod.rs +++ b/src/modules/upgrade/mod.rs @@ -1,3 +1,4 @@ pub mod impls; pub mod path; +pub mod query; pub mod service; diff --git a/src/modules/upgrade/query.rs b/src/modules/upgrade/query.rs new file mode 100644 index 00000000..dd1b3ab7 --- /dev/null +++ b/src/modules/upgrade/query.rs @@ -0,0 +1,27 @@ +use ibc::hosts::tendermint::upgrade_proposal::Plan; +use ibc_proto::cosmos::upgrade::v1beta1::Plan as RawPlan; +use ibc_proto::protobuf::Protobuf; +use tendermint_rpc::{Client, HttpClient}; + +use super::path::UpgradePlanPath; +use crate::config::CometbftConfig; +use crate::error::Error; + +pub(crate) const UPGRADE_PLAN_QUERY_PATH: &str = "/cosmos.upgrade.v1beta1.Query/CurrentPlan"; + +pub async fn query_upgrade_plan(cfg: CometbftConfig) -> Result { + let rpc_client = HttpClient::new(cfg.rpc_addr.clone()).unwrap(); + + let data = UpgradePlanPath::sdk_pending_path().to_string().into_bytes(); + + let response = rpc_client + .abci_query(Some(UPGRADE_PLAN_QUERY_PATH.to_string()), data, None, false) + .await + .map_err(|e| Error::Custom { + reason: e.to_string(), + })?; + + let plan = Protobuf::::decode_vec(&response.value).unwrap(); + + Ok(plan) +}