diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dad03ab4..de80e38b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,10 +72,10 @@ jobs: run: cargo build --all --target=wasm32-unknown-unknown --all-features - name: Test proto crate - run: wasm-pack test --node proto + run: wasm-pack test --headless --chrome proto - name: Test types crate - run: wasm-pack test --node types --features=wasm-bindgen + run: wasm-pack test --headless --chrome types --features=wasm-bindgen - name: Test node crate run: wasm-pack test --headless --chrome node diff --git a/Cargo.lock b/Cargo.lock index 66d927ef..0f6544e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,7 +304,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "synstructure", ] @@ -316,7 +316,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -349,6 +349,28 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "async-trait" version = "0.1.80" @@ -357,7 +379,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -398,7 +420,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -420,7 +442,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.3.1", + "hyper 1.5.1", "hyper-util", "itoa", "matchit", @@ -497,6 +519,18 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -672,7 +706,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "syn_derive", ] @@ -705,9 +739,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" dependencies = [ "serde", ] @@ -718,17 +752,53 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +[[package]] +name = "celestia-grpc" +version = "0.1.0" +dependencies = [ + "anyhow", + "celestia-grpc-macros", + "celestia-proto", + "celestia-tendermint", + "celestia-tendermint-proto", + "celestia-types", + "dotenvy", + "hex", + "k256", + "pbjson-types", + "prost", + "serde", + "thiserror", + "tokio", + "tonic", +] + +[[package]] +name = "celestia-grpc-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "celestia-proto" version = "0.5.0" dependencies = [ "celestia-tendermint-proto", + "pbjson", + "pbjson-build", + "pbjson-types", "prost", "prost-build", "prost-types", "protox", "serde", "serde_json", + "tempfile", + "tonic", + "tonic-build", "wasm-bindgen-test", ] @@ -738,6 +808,8 @@ version = "0.7.1" dependencies = [ "anyhow", "async-trait", + "celestia-tendermint", + "celestia-tendermint-proto", "celestia-types", "dotenvy", "futures", @@ -746,6 +818,7 @@ dependencies = [ "jsonrpsee", "libp2p", "nmt-rs", + "prost", "rand", "serde", "thiserror", @@ -756,9 +829,9 @@ dependencies = [ [[package]] name = "celestia-tendermint" -version = "0.32.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8c92a01145f79a0f3ac7c44a43a9b5ee58e8a4c716b56d98833a3848db1afd" +checksum = "cb4529cd69c16e0f75b40f87ed281b602d01e99e1ad0a7a5b147ec93e8711001" dependencies = [ "bytes", "celestia-tendermint-proto", @@ -768,10 +841,12 @@ dependencies = [ "flex-error", "futures", "instant", + "k256", "num-traits", "once_cell", "prost", "prost-types", + "ripemd", "serde", "serde_bytes", "serde_json", @@ -786,9 +861,9 @@ dependencies = [ [[package]] name = "celestia-tendermint-proto" -version = "0.32.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a95746c5221a74d7b913a415fdbb9e7c90e1b4d818dbbff59bddc034cfce2ec" +checksum = "263e3443a0e60c0f5a407a62f2486217a20cd28617cdaa3ec6546d288728b91d" dependencies = [ "bytes", "flex-error", @@ -806,8 +881,9 @@ dependencies = [ name = "celestia-types" version = "0.8.0" dependencies = [ - "base64", + "base64 0.22.1", "bech32", + "bitvec", "blockstore", "bytes", "celestia-proto", @@ -824,7 +900,9 @@ dependencies = [ "multiaddr", "multihash", "nmt-rs", + "pbjson-types", "prost", + "prost-types", "rand", "ruint", "serde", @@ -878,6 +956,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "num-traits", +] + [[package]] name = "cid" version = "0.11.1" @@ -934,7 +1021,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -1065,6 +1152,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1109,7 +1208,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -1132,7 +1231,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -1146,7 +1245,7 @@ checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" dependencies = [ "cfg-if", "crossbeam-utils", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -1238,6 +1337,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -1280,7 +1380,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -1295,6 +1395,20 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -1341,6 +1455,25 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "enum-as-inner" version = "0.6.0" @@ -1350,7 +1483,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -1362,7 +1495,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -1414,9 +1547,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fastrlp" @@ -1429,6 +1562,16 @@ dependencies = [ "bytes", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1501,9 +1644,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1526,9 +1669,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1536,15 +1679,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1554,9 +1697,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1570,13 +1713,13 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -1592,15 +1735,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-ticker" @@ -1625,9 +1768,9 @@ dependencies = [ [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1649,6 +1792,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1744,6 +1888,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" version = "0.3.26" @@ -1756,7 +1911,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1775,13 +1930,19 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1991,9 +2152,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", @@ -2018,7 +2179,7 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.3.1", + "hyper 1.5.1", "hyper-util", "log", "rustls", @@ -2028,22 +2189,34 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.5.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.0", - "hyper 1.3.1", + "hyper 1.5.1", "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] @@ -2163,7 +2336,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -2276,6 +2449,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -2283,7 +2466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -2348,9 +2531,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -2411,7 +2594,7 @@ version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be764c8b96cdcd2974655560a1c6542a366440d47c88114894cc20c24317815" dependencies = [ - "base64", + "base64 0.22.1", "futures-channel", "futures-util", "gloo-net", @@ -2462,9 +2645,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d5f8f6ddb09312a9592ec9e21a3ccd6f61e51730d9d56321351eff971b0fe55" dependencies = [ "async-trait", - "base64", + "base64 0.22.1", "http-body 1.0.0", - "hyper 1.3.1", + "hyper 1.5.1", "hyper-rustls", "hyper-util", "jsonrpsee-core", @@ -2490,7 +2673,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -2529,6 +2712,20 @@ dependencies = [ "url", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.8", + "signature", +] + [[package]] name = "keccak" version = "0.1.5" @@ -2557,9 +2754,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libm" @@ -2737,7 +2934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4e830fdf24ac8c444c12415903174d506e1e077fbe3875c404a78c5935a8543" dependencies = [ "asynchronous-codec", - "base64", + "base64 0.22.1", "byteorder", "bytes", "either", @@ -2997,7 +3194,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -3210,7 +3407,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.8.4", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -3228,7 +3425,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -3395,7 +3592,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -3531,7 +3728,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "synstructure", ] @@ -3904,13 +4101,50 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbjson" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e6349fa080353f4a597daffd05cb81572a9c031a6d4fff7e504947496fcc68" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eea3058763d6e656105d1403cb04e0a41b7bbac6362d413e7c33be0c32279c9" +dependencies = [ + "heck 0.5.0", + "itertools 0.13.0", + "prost", + "prost-types", +] + +[[package]] +name = "pbjson-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54e5e7bfb1652f95bc361d76f3c780d8e526b134b85417e774166ee941f0887" +dependencies = [ + "bytes", + "chrono", + "pbjson", + "pbjson-build", + "prost", + "prost-build", + "serde", +] + [[package]] name = "pem" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64", + "base64 0.22.1", "serde", ] @@ -3938,7 +4172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 2.2.6", ] [[package]] @@ -3958,7 +4192,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -4040,7 +4274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -4098,9 +4332,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -4125,7 +4359,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -4146,9 +4380,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.6" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", "prost-derive", @@ -4156,13 +4390,13 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.6" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" dependencies = [ "bytes", - "heck 0.4.1", - "itertools 0.12.1", + "heck 0.5.0", + "itertools 0.13.0", "log", "multimap", "once_cell", @@ -4171,28 +4405,28 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.66", + "syn 2.0.87", "tempfile", ] [[package]] name = "prost-derive" -version = "0.12.6" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] name = "prost-reflect" -version = "0.13.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5eec97d5d34bdd17ad2db2219aabf46b054c6c41bd5529767c9ce55be5898f" +checksum = "4b7535b02f0e5efe3e1dbfcb428be152226ed0c66cad9541f2274c8ba8d4cd40" dependencies = [ "logos", "miette", @@ -4203,18 +4437,18 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.6" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" dependencies = [ "prost", ] [[package]] name = "protox" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac532509cee918d40f38c3e12f8ef9230f215f017d54de7dd975015538a42ce7" +checksum = "873f359bdecdfe6e353752f97cb9ee69368df55b16363ed2216da85e03232a58" dependencies = [ "bytes", "miette", @@ -4227,9 +4461,9 @@ dependencies = [ [[package]] name = "protox-parse" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6c33f43516fe397e2f930779d720ca12cd057f7da4cd6326a0ef78d69dee96" +checksum = "a3a462d115462c080ae000c29a47f0b3985737e5d3a995fcdbcaa5c782068dde" dependencies = [ "logos", "miette", @@ -4315,9 +4549,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -4479,6 +4713,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -4554,7 +4798,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.0", - "syn 2.0.66", + "syn 2.0.87", "unicode-ident", ] @@ -4624,7 +4868,7 @@ dependencies = [ "quote", "rust-embed-utils", "shellexpand", - "syn 2.0.66", + "syn 2.0.87", "walkdir", ] @@ -4691,9 +4935,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.5.0", "errno", @@ -4736,7 +4980,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64", + "base64 0.22.1", "rustls-pki-types", ] @@ -4847,6 +5091,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.0" @@ -4912,9 +5170,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -4941,13 +5199,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -4979,7 +5237,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -5063,6 +5321,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ + "digest 0.10.7", "rand_core", ] @@ -5117,7 +5376,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37468c595637c10857701c990f93a40ce0e357cedb0953d1c26c8d8027f9bb53" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures", "httparse", @@ -5213,9 +5472,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -5231,7 +5490,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -5254,7 +5513,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -5286,12 +5545,13 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", "windows-sys 0.52.0", ] @@ -5313,7 +5573,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -5408,7 +5668,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -5424,9 +5684,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -5459,7 +5719,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -5470,11 +5730,55 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap", + "indexmap 2.2.6", "toml_datetime", "winnow", ] +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.5.1", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.87", +] + [[package]] name = "tower" version = "0.4.13" @@ -5483,9 +5787,13 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", + "indexmap 1.9.3", "pin-project", "pin-project-lite", + "rand", + "slab", "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -5535,7 +5843,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -5797,7 +6105,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -5831,7 +6139,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5865,7 +6173,7 @@ checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -6242,7 +6550,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "synstructure", ] @@ -6263,7 +6571,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -6283,7 +6591,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", "synstructure", ] @@ -6304,7 +6612,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] [[package]] @@ -6326,5 +6634,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.87", ] diff --git a/Cargo.toml b/Cargo.toml index f906f899..f69353a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,23 @@ [workspace] resolver = "2" -members = ["cli", "node", "node-wasm", "proto", "rpc", "types"] +members = ["cli", "grpc", "node", "node-wasm", "proto", "rpc", "types"] [workspace.dependencies] blockstore = "0.7.0" lumina-node = { version = "0.7.0", path = "node" } lumina-node-wasm = { version = "0.6.1", path = "node-wasm" } celestia-proto = { version = "0.5.0", path = "proto" } +celestia-grpc = { version = "0.1.0", path = "grpc" } celestia-rpc = { version = "0.7.1", path = "rpc", default-features = false } celestia-types = { version = "0.8.0", path = "types", default-features = false } -celestia-tendermint = { version = "0.32.2", default-features = false } -celestia-tendermint-proto = "0.32.2" +celestia-tendermint = { version = "0.33.0", default-features = false } +celestia-tendermint-proto = "0.33.0" libp2p = "0.54.0" nmt-rs = "0.2.1" -prost = "0.12.6" +prost = "0.13.3" +prost-build = "0.13.3" +prost-types = "0.13.3" [patch.crates-io] # Uncomment to apply local changes diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index 81191648..680afc45 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -8,6 +8,8 @@ services: environment: # provide amount of bridge nodes to provision (default: 2) - BRIDGE_COUNT=2 + ports: + - 19090:9090 volumes: - credentials:/credentials - genesis:/genesis diff --git a/ci/run-validator.sh b/ci/run-validator.sh index 573f1d10..21a8e703 100755 --- a/ci/run-validator.sh +++ b/ci/run-validator.sh @@ -59,6 +59,7 @@ provision_bridge_nodes() { for node_idx in $(seq 0 "$last_node_idx"); do local bridge_name="bridge-$node_idx" local key_file="$CREDENTIALS_DIR/$bridge_name.key" + local plaintext_key_file="$CREDENTIALS_DIR/$bridge_name.plaintext-key" local addr_file="$CREDENTIALS_DIR/$bridge_name.addr" if [ ! -e "$key_file" ]; then @@ -67,6 +68,8 @@ provision_bridge_nodes() { celestia-appd keys add "$bridge_name" --keyring-backend "test" # export it echo "password" | celestia-appd keys export "$bridge_name" 2> "$key_file.lock" + # export also plaintext key for convenience in tests + echo y | celestia-appd keys export "$bridge_name" --unsafe --unarmored-hex 2> "${plaintext_key_file}" # the `.lock` file and `mv` ensures that readers read file only after finished writing mv "$key_file.lock" "$key_file" # export associated address diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 4ce24858..afe409aa 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -25,9 +25,9 @@ path = "src/main.rs" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] celestia-rpc = { workspace = true, features = ["p2p"] } -celestia-types = { workspace = true } -libp2p = { workspace = true } -lumina-node = { workspace = true } +celestia-types.workspace = true +libp2p.workspace = true +lumina-node.workspace = true anyhow = "1.0.86" axum = "0.7.5" diff --git a/grpc/Cargo.toml b/grpc/Cargo.toml new file mode 100644 index 00000000..7038c079 --- /dev/null +++ b/grpc/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "celestia-grpc" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +description = "A client for interacting with Celestia validator nodes gRPC" +authors = ["Eiger "] +homepage = "https://www.eiger.co" +repository = "https://github.com/eigerco/lumina" +readme = "README.md" +# crates.io is limited to 5 keywords and 5 categories +keywords = ["blockchain", "celestia", "lumina"] +# Must be one of +categories = [ + "api-bindings", + "asynchronous", + "encoding", + "cryptography::cryptocurrencies", +] + +[dependencies] +celestia-tendermint-proto.workspace = true +celestia-types = { workspace = true, features = [ "tonic" ] } +celestia-proto = { workspace = true, features = [ "tonic" ] } +celestia-tendermint.workspace = true +prost.workspace = true +celestia-grpc-macros = { version = "0.1.0", path = "grpc-macros" } + +hex = "0.4.3" +k256 = "0.13.4" +pbjson-types = "0.7.0" +serde = "1.0.215" +thiserror = "1.0.61" +tonic = { version = "0.12.3", default-features = false, features = [ + "codegen", "prost" +]} + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +anyhow = "1.0.86" +dotenvy = "0.15.7" +tokio = { version = "1.38.0", features = ["rt", "macros"] } +tonic = { version = "0.12.3", optional = true, default-features = false, features = [ "transport" ] } diff --git a/grpc/README.md b/grpc/README.md new file mode 100644 index 00000000..83ca75ff --- /dev/null +++ b/grpc/README.md @@ -0,0 +1,5 @@ +# Celestia gRPC + +A collection of types for interacting with Celestia validator nodes over gRPC + +This crate builds on top of [`tonic`](https://docs.rs/tonic). diff --git a/grpc/grpc-macros/Cargo.toml b/grpc/grpc-macros/Cargo.toml new file mode 100644 index 00000000..31318038 --- /dev/null +++ b/grpc/grpc-macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "celestia-grpc-macros" +version = "0.1.0" +edition = "2021" + +[dependencies] +quote = "1.0.37" +syn = "2.0.87" +proc-macro2 = "1.0.89" + +[lib] +proc-macro = true diff --git a/grpc/grpc-macros/src/lib.rs b/grpc/grpc-macros/src/lib.rs new file mode 100644 index 00000000..f2656f2e --- /dev/null +++ b/grpc/grpc-macros/src/lib.rs @@ -0,0 +1,128 @@ +//! Helper crate for grpc_method macro for creating gRPC methods +//! +//! # Example +//! ```rust,ignore +//! use celestia_proto::cosmos::auth::v1beta1::query_client::QueryClient; +//! # use tonic::service::Interceptor; +//! # use tonic::transport::Channel; +//! # +//! # pub struct GrpcClient +//! # where +//! # I: Interceptor, +//! # { +//! # grpc_channel: Channel, +//! # auth_interceptor: I, +//! # } +//! +//! impl GrpcClient +//! where +//! I: Interceptor + Clone, +//! { +//! /// Get auth params +//! #[grpc_method(AuthQueryClient::params)] +//! async fn get_auth_params(&mut self) -> Result; +//! } +//! ``` + +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::{Group, TokenStream as TokenStream2}; +use quote::{quote, TokenStreamExt}; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::{parse_macro_input, Attribute, FnArg, Ident, Signature, Token}; + +#[derive(Debug)] +struct GrpcMethod { + doc_hash: Token![#], + doc_group: Group, + attrs: Vec, + signature: Signature, + _terminating_semi: Token![;], +} + +impl Parse for GrpcMethod { + fn parse(input: ParseStream) -> syn::Result { + Ok(GrpcMethod { + doc_hash: input.parse()?, + doc_group: input.parse()?, + attrs: input.call(Attribute::parse_inner)?, + signature: input.parse()?, + _terminating_semi: input.parse()?, + }) + } +} + +impl GrpcMethod { + fn instantiate_method(&self, tonic_method: GrpcMethodAttribute) -> TokenStream2 { + let mut tokens = TokenStream2::new(); + + tokens.append_all(&self.attrs); + + let grpc_client_struct = tonic_method.client; + let grpc_method_name = tonic_method.method; + + let doc_hash = self.doc_hash; + let doc_group = &self.doc_group; + + let signature = self.signature.clone(); + let params: Vec<_> = self + .signature + .inputs + .iter() + .filter_map(|arg| { + let FnArg::Typed(arg) = arg else { + return None; + }; + Some(&arg.pat) + }) + .collect(); + + let method = quote! { + #doc_hash #doc_group + pub #signature { + let mut client = #grpc_client_struct :: with_interceptor( + self.grpc_channel.clone(), + self.auth_interceptor.clone(), + ); + let request = ::tonic::Request::new(( #( #params ),* ).into_parameter()); + let response = client. #grpc_method_name (request).await; + response?.into_inner().try_from_response() + } + }; + + tokens.extend(method); + + tokens + } +} + +#[derive(Debug)] +struct GrpcMethodAttribute { + method: Ident, + client: Punctuated, +} + +impl Parse for GrpcMethodAttribute { + fn parse(input: ParseStream) -> syn::Result { + let mut parsed = Punctuated::::parse_separated_nonempty(input)?; + + let method = parsed.pop().expect("expected client method").into_value(); + parsed.pop_punct(); + let client = parsed; + + Ok(GrpcMethodAttribute { method, client }) + } +} + +/// Annotate a function signature passing ServiceClient method to be called +#[proc_macro_attribute] +pub fn grpc_method(attr: TokenStream, item: TokenStream) -> TokenStream { + let attributes = parse_macro_input!(attr as GrpcMethodAttribute); + let method_sig = parse_macro_input!(item as GrpcMethod); + + let method = method_sig.instantiate_method(attributes); + + method.into() +} diff --git a/grpc/src/client.rs b/grpc/src/client.rs new file mode 100644 index 00000000..4962b582 --- /dev/null +++ b/grpc/src/client.rs @@ -0,0 +1,110 @@ +use prost::Message; +use tonic::service::Interceptor; +use tonic::transport::Channel; + +use celestia_proto::celestia::blob::v1::query_client::QueryClient as BlobQueryClient; +use celestia_proto::cosmos::auth::v1beta1::query_client::QueryClient as AuthQueryClient; +use celestia_proto::cosmos::base::node::v1beta1::service_client::ServiceClient as ConfigServiceClient; +use celestia_proto::cosmos::base::tendermint::v1beta1::service_client::ServiceClient as TendermintServiceClient; +use celestia_proto::cosmos::tx::v1beta1::service_client::ServiceClient as TxServiceClient; +use celestia_tendermint::block::Block; +use celestia_types::blob::{Blob, BlobParams, RawBlobTx}; +use celestia_types::state::auth::AuthParams; +use celestia_types::state::Address; +use celestia_types::state::{RawTx, TxResponse}; + +use celestia_grpc_macros::grpc_method; + +use crate::types::auth::Account; +use crate::types::tx::GetTxResponse; +use crate::types::{FromGrpcResponse, IntoGrpcParam}; +use crate::Error; + +pub use celestia_proto::cosmos::tx::v1beta1::BroadcastMode; + +/// Struct wrapping all the tonic types and doing type conversion behind the scenes. +pub struct GrpcClient +where + I: Interceptor, +{ + grpc_channel: Channel, + auth_interceptor: I, +} + +impl GrpcClient +where + I: Interceptor + Clone, +{ + /// Create a new client out of channel and optional auth + pub fn new(grpc_channel: Channel, auth_interceptor: I) -> Self { + Self { + grpc_channel, + auth_interceptor, + } + } + + /// Get Minimum Gas price + #[grpc_method(ConfigServiceClient::config)] + async fn get_min_gas_price(&mut self) -> Result; + + /// Get latest block + #[grpc_method(TendermintServiceClient::get_latest_block)] + async fn get_latest_block(&mut self) -> Result; + + /// Get block by height + #[grpc_method(TendermintServiceClient::get_block_by_height)] + async fn get_block_by_height(&mut self, height: i64) -> Result; + + /// Get blob params + #[grpc_method(BlobQueryClient::params)] + async fn get_blob_params(&mut self) -> Result; + + /// Get auth params + #[grpc_method(AuthQueryClient::params)] + async fn get_auth_params(&mut self) -> Result; + + /// Get account + #[grpc_method(AuthQueryClient::account)] + async fn get_account(&mut self, account: &Address) -> Result; + + // TODO: pagination? + /// Get accounts + #[grpc_method(AuthQueryClient::accounts)] + async fn get_accounts(&mut self) -> Result, Error>; + + /// Broadcast prepared and serialised transaction + #[grpc_method(TxServiceClient::broadcast_tx)] + async fn broadcast_tx( + &mut self, + tx_bytes: Vec, + mode: BroadcastMode, + ) -> Result; + + /// Broadcast blob transaction + pub async fn broadcast_blob_tx( + &mut self, + tx: RawTx, + blobs: Vec, + mode: BroadcastMode, + ) -> Result { + // From https://github.com/celestiaorg/celestia-core/blob/v1.43.0-tm-v0.34.35/pkg/consts/consts.go#L19 + const BLOB_TX_TYPE_ID: &str = "BLOB"; + + if blobs.is_empty() { + return Err(Error::TxEmptyBlobList); + } + + let blobs = blobs.into_iter().map(Into::into).collect(); + let blob_tx = RawBlobTx { + tx: tx.encode_to_vec(), + blobs, + type_id: BLOB_TX_TYPE_ID.to_string(), + }; + + self.broadcast_tx(blob_tx.encode_to_vec(), mode).await + } + + /// Get Tx + #[grpc_method(TxServiceClient::get_tx)] + async fn get_tx(&mut self, hash: String) -> Result; +} diff --git a/grpc/src/error.rs b/grpc/src/error.rs new file mode 100644 index 00000000..9cdb2c7c --- /dev/null +++ b/grpc/src/error.rs @@ -0,0 +1,40 @@ +use tonic::Status; + +/// Alias for a `Result` with the error type [`celestia_tonic::Error`]. +/// +/// [`celestia_tonic::Error`]: crate::Error +pub type Result = std::result::Result; + +/// Representation of all the errors that can occur when interacting with [`celestia_tonic`]. +/// +/// [`celestia_tonic`]: crate +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Tonic error + #[error(transparent)] + TonicError(#[from] Status), + + /// Tendermint Error + #[error(transparent)] + TendermintError(#[from] celestia_tendermint::Error), + + /// Celestia types error + #[error(transparent)] + CelestiaTypesError(#[from] celestia_types::Error), + + /// Tendermint Proto Error + #[error(transparent)] + TendermintProtoError(#[from] celestia_tendermint_proto::Error), + + /// Failed to parse gRPC response + #[error("Failed to parse response")] + FailedToParseResponse, + + /// Unexpected reponse type + #[error("Unexpected response type")] + UnexpectedResponseType(String), + + /// Empty blob submission list + #[error("Attempted to submit blob transaction with empty blob list")] + TxEmptyBlobList, +} diff --git a/grpc/src/lib.rs b/grpc/src/lib.rs new file mode 100644 index 00000000..9e68cbfa --- /dev/null +++ b/grpc/src/lib.rs @@ -0,0 +1,9 @@ +#![doc = include_str!("../README.md")] +#![cfg(not(target_arch = "wasm32"))] + +mod client; +mod error; +pub mod types; + +pub use crate::client::GrpcClient; +pub use crate::error::{Error, Result}; diff --git a/grpc/src/types.rs b/grpc/src/types.rs new file mode 100644 index 00000000..ead99ebf --- /dev/null +++ b/grpc/src/types.rs @@ -0,0 +1,87 @@ +//! Custom types and wrappers needed by gRPC + +use celestia_proto::celestia::blob::v1::{ + QueryParamsRequest as QueryBlobParamsRequest, QueryParamsResponse as QueryBlobParamsResponse, +}; +use celestia_proto::cosmos::base::node::v1beta1::{ConfigRequest, ConfigResponse}; +use celestia_proto::cosmos::base::tendermint::v1beta1::{ + GetBlockByHeightRequest, GetBlockByHeightResponse, GetLatestBlockRequest, + GetLatestBlockResponse, +}; +use celestia_tendermint::block::Block; +use celestia_types::blob::BlobParams; + +use crate::Error; + +/// types related to authorisation +pub mod auth; +/// types related to transaction querying and submission +pub mod tx; + +macro_rules! make_empty_params { + ($request_type:ident) => { + impl IntoGrpcParam<$request_type> for () { + fn into_parameter(self) -> $request_type { + $request_type {} + } + } + }; +} + +pub(crate) use make_empty_params; + +pub(crate) trait FromGrpcResponse { + fn try_from_response(self) -> Result; +} + +pub(crate) trait IntoGrpcParam { + fn into_parameter(self) -> T; +} + +impl FromGrpcResponse for QueryBlobParamsResponse { + fn try_from_response(self) -> Result { + let params = self.params.ok_or(Error::FailedToParseResponse)?; + Ok(BlobParams { + gas_per_blob_byte: params.gas_per_blob_byte, + gov_max_square_size: params.gov_max_square_size, + }) + } +} + +impl FromGrpcResponse for GetBlockByHeightResponse { + fn try_from_response(self) -> Result { + Ok(self.block.ok_or(Error::FailedToParseResponse)?.try_into()?) + } +} + +impl FromGrpcResponse for GetLatestBlockResponse { + fn try_from_response(self) -> Result { + Ok(self.block.ok_or(Error::FailedToParseResponse)?.try_into()?) + } +} + +impl FromGrpcResponse for ConfigResponse { + fn try_from_response(self) -> Result { + const UNITS_SUFFIX: &str = "utia"; + + let min_gas_price_with_suffix = self.minimum_gas_price; + let min_gas_price_str = min_gas_price_with_suffix + .strip_suffix(UNITS_SUFFIX) + .ok_or(Error::FailedToParseResponse)?; + let min_gas_price = min_gas_price_str + .parse::() + .map_err(|_| Error::FailedToParseResponse)?; + + Ok(min_gas_price) + } +} + +impl IntoGrpcParam for i64 { + fn into_parameter(self) -> GetBlockByHeightRequest { + GetBlockByHeightRequest { height: self } + } +} + +make_empty_params!(GetLatestBlockRequest); +make_empty_params!(ConfigRequest); +make_empty_params!(QueryBlobParamsRequest); diff --git a/grpc/src/types/auth.rs b/grpc/src/types/auth.rs new file mode 100644 index 00000000..330f13ca --- /dev/null +++ b/grpc/src/types/auth.rs @@ -0,0 +1,91 @@ +use pbjson_types::Any; +use prost::{Message, Name}; + +use celestia_proto::cosmos::auth::v1beta1::{ + QueryAccountRequest, QueryAccountResponse, QueryAccountsRequest, QueryAccountsResponse, + QueryParamsRequest as QueryAuthParamsRequest, QueryParamsResponse as QueryAuthParamsResponse, +}; +use celestia_types::state::auth::{ + AuthParams, BaseAccount, ModuleAccount, RawBaseAccount, RawModuleAccount, +}; +use celestia_types::state::Address; + +use crate::types::make_empty_params; +use crate::types::{FromGrpcResponse, IntoGrpcParam}; +use crate::Error; + +/// Enum representing different types of account +#[derive(Debug, PartialEq)] +pub enum Account { + /// Base account type + Base(BaseAccount), + /// Account for modules that holds coins on a pool + Module(ModuleAccount), +} + +impl Account { + /// Return [`BaseAccount`] reference, if it exists, from either Base or Module account + pub fn base_account_ref(&self) -> Option<&BaseAccount> { + match self { + Account::Base(acct) => Some(acct), + Account::Module(acct) => acct.base_account.as_ref(), + } + } +} + +impl FromGrpcResponse for QueryAuthParamsResponse { + fn try_from_response(self) -> Result { + let params = self.params.ok_or(Error::FailedToParseResponse)?; + Ok(AuthParams { + max_memo_characters: params.max_memo_characters, + tx_sig_limit: params.tx_sig_limit, + tx_size_cost_per_byte: params.tx_size_cost_per_byte, + sig_verify_cost_ed25519: params.sig_verify_cost_ed25519, + sig_verify_cost_secp256k1: params.sig_verify_cost_secp256k1, + }) + } +} + +impl FromGrpcResponse for QueryAccountResponse { + fn try_from_response(self) -> Result { + account_from_any(self.account.ok_or(Error::FailedToParseResponse)?) + } +} + +impl FromGrpcResponse> for QueryAccountsResponse { + fn try_from_response(self) -> Result, Error> { + self.accounts.into_iter().map(account_from_any).collect() + } +} + +make_empty_params!(QueryAuthParamsRequest); + +impl IntoGrpcParam for &Address { + fn into_parameter(self) -> QueryAccountRequest { + QueryAccountRequest { + address: self.to_string(), + } + } +} + +impl IntoGrpcParam for () { + fn into_parameter(self) -> QueryAccountsRequest { + QueryAccountsRequest { pagination: None } + } +} + +fn account_from_any(any: Any) -> Result { + let account = if any.type_url == RawBaseAccount::type_url() { + let base_account = + RawBaseAccount::decode(&*any.value).map_err(|_| Error::FailedToParseResponse)?; + Account::Base(base_account.try_into()?) + } else if any.type_url == RawModuleAccount::type_url() { + let module_account = + RawModuleAccount::decode(&*any.value).map_err(|_| Error::FailedToParseResponse)?; + Account::Module(module_account.try_into()?) + } else { + return Err(Error::UnexpectedResponseType(any.type_url)); + }; + + Ok(account) +} diff --git a/grpc/src/types/tx.rs b/grpc/src/types/tx.rs new file mode 100644 index 00000000..9297f75d --- /dev/null +++ b/grpc/src/types/tx.rs @@ -0,0 +1,133 @@ +use std::convert::Infallible; + +use k256::ecdsa::{signature::Signer, Signature}; +use pbjson_types::Any; +use prost::{Message, Name}; + +use celestia_proto::cosmos::crypto::secp256k1; +use celestia_proto::cosmos::tx::v1beta1::{ + BroadcastTxRequest, BroadcastTxResponse, GetTxRequest as RawGetTxRequest, + GetTxResponse as RawGetTxResponse, SignDoc, +}; +use celestia_tendermint::public_key::Secp256k1 as VerifyingKey; +use celestia_tendermint_proto::Protobuf; +use celestia_types::state::auth::BaseAccount; +use celestia_types::state::{ + AuthInfo, Fee, ModeInfo, RawTx, RawTxBody, SignerInfo, Sum, Tx, TxResponse, +}; + +use crate::types::{FromGrpcResponse, IntoGrpcParam}; +use crate::Error; + +pub use celestia_proto::cosmos::tx::v1beta1::BroadcastMode; + +/// Response to GetTx +#[derive(Debug)] +pub struct GetTxResponse { + /// Response Transaction + pub tx: Tx, + + /// TxResponse to a Query + pub tx_response: TxResponse, +} + +impl FromGrpcResponse for BroadcastTxResponse { + fn try_from_response(self) -> Result { + Ok(self + .tx_response + .ok_or(Error::FailedToParseResponse)? + .try_into()?) + } +} + +impl FromGrpcResponse for RawGetTxResponse { + fn try_from_response(self) -> Result { + let tx_response = self + .tx_response + .ok_or(Error::FailedToParseResponse)? + .try_into()?; + + let tx = self.tx.ok_or(Error::FailedToParseResponse)?; + + let cosmos_tx = Tx { + body: tx.body.ok_or(Error::FailedToParseResponse)?.try_into()?, + auth_info: tx + .auth_info + .ok_or(Error::FailedToParseResponse)? + .try_into()?, + signatures: tx.signatures, + }; + + Ok(GetTxResponse { + tx: cosmos_tx, + tx_response, + }) + } +} + +impl IntoGrpcParam for (Vec, BroadcastMode) { + fn into_parameter(self) -> BroadcastTxRequest { + let (tx_bytes, mode) = self; + + BroadcastTxRequest { + tx_bytes, + mode: mode.into(), + } + } +} + +impl IntoGrpcParam for String { + fn into_parameter(self) -> RawGetTxRequest { + RawGetTxRequest { hash: self } + } +} + +/// Sign `tx_body` and the transaction metadata as the `base_account` using `signer` +pub fn sign_tx( + tx_body: RawTxBody, + chain_id: String, + base_account: &BaseAccount, + verifying_key: VerifyingKey, + signer: impl Signer, + gas_limit: u64, + fee: u64, +) -> RawTx { + // From https://github.com/celestiaorg/cosmos-sdk/blob/v1.25.0-sdk-v0.46.16/proto/cosmos/tx/signing/v1beta1/signing.proto#L24 + const SIGNING_MODE_INFO: ModeInfo = ModeInfo { + sum: Sum::Single { mode: 1 }, + }; + + let public_key = secp256k1::PubKey { + key: verifying_key.to_encoded_point(true).as_bytes().to_vec(), + }; + let public_key_as_any = Any { + type_url: secp256k1::PubKey::type_url(), + value: public_key.encode_to_vec().into(), + }; + + let auth_info = AuthInfo { + signer_infos: vec![SignerInfo { + public_key: Some(public_key_as_any), + mode_info: SIGNING_MODE_INFO, + sequence: base_account.sequence, + }], + fee: Fee::new(fee, gas_limit), + }; + let auth_info_bytes: Result<_, Infallible> = auth_info.encode_vec(); + + let bytes_to_sign = SignDoc { + body_bytes: tx_body.encode_to_vec(), + auth_info_bytes: auth_info_bytes.expect("Result to be Infallible"), + chain_id, + account_number: base_account.account_number, + } + .encode_to_vec(); + + let signature: Signature = signer.sign(&bytes_to_sign); + + RawTx { + auth_info: Some(auth_info.into()), + body: Some(tx_body), + signatures: vec![signature.to_bytes().to_vec()], + } +} diff --git a/grpc/tests/tonic.rs b/grpc/tests/tonic.rs new file mode 100644 index 00000000..07d1387a --- /dev/null +++ b/grpc/tests/tonic.rs @@ -0,0 +1,110 @@ +#![cfg(not(target_arch = "wasm32"))] + +use celestia_grpc::types::auth::Account; +use celestia_grpc::types::tx::sign_tx; +use celestia_proto::cosmos::tx::v1beta1::BroadcastMode; +use celestia_types::blob::MsgPayForBlobs; +use celestia_types::nmt::Namespace; +use celestia_types::{AppVersion, Blob}; + +pub mod utils; + +use crate::utils::{load_account, new_test_client}; + +const BRIDGE_0_ACCOUNT_DATA: &str = "../ci/credentials/bridge-0"; + +#[tokio::test] +async fn get_min_gas_price() { + let mut client = new_test_client().await.unwrap(); + let gas_price = client.get_min_gas_price().await.unwrap(); + assert!(gas_price > 0.0); +} + +#[tokio::test] +async fn get_blob_params() { + let mut client = new_test_client().await.unwrap(); + let params = client.get_blob_params().await.unwrap(); + assert!(params.gas_per_blob_byte > 0); + assert!(params.gov_max_square_size > 0); +} + +#[tokio::test] +async fn get_auth_params() { + let mut client = new_test_client().await.unwrap(); + let params = client.get_auth_params().await.unwrap(); + assert!(params.max_memo_characters > 0); + assert!(params.tx_sig_limit > 0); + assert!(params.tx_size_cost_per_byte > 0); + assert!(params.sig_verify_cost_ed25519 > 0); + assert!(params.sig_verify_cost_secp256k1 > 0); +} + +#[tokio::test] +async fn get_block() { + let mut client = new_test_client().await.unwrap(); + + let latest_block = client.get_latest_block().await.unwrap(); + let height = latest_block.header.height.value() as i64; + + let block = client.get_block_by_height(height).await.unwrap(); + assert_eq!(block.header, latest_block.header); +} + +#[tokio::test] +async fn get_account() { + let mut client = new_test_client().await.unwrap(); + + let accounts = client.get_accounts().await.unwrap(); + + let first_account = accounts.first().expect("account to exist"); + + let address = match first_account { + Account::Base(acct) => acct.address.clone(), + Account::Module(acct) => acct.base_account.as_ref().unwrap().address.clone(), + }; + + let account = client.get_account(&address).await.unwrap(); + + assert_eq!(&account, first_account); +} + +#[tokio::test] +async fn submit_blob() { + let mut client = new_test_client().await.unwrap(); + + let account_credentials = load_account(BRIDGE_0_ACCOUNT_DATA); + let namespace = Namespace::new_v0(&[1, 2, 3]).unwrap(); + let blobs = vec![Blob::new(namespace, "Hello, World!".into(), AppVersion::V3).unwrap()]; + let chain_id = "private".to_string(); + let account = client + .get_account(&account_credentials.address) + .await + .unwrap(); + // gas and fees are overestimated for simplicity + let gas_limit = 100000; + let fee = 5000; + + let msg_pay_for_blobs = MsgPayForBlobs::new(&blobs, account_credentials.address).unwrap(); + + let tx = sign_tx( + msg_pay_for_blobs.into(), + chain_id, + account.base_account_ref().unwrap(), + account_credentials.verifying_key, + account_credentials.signing_key, + gas_limit, + fee, + ); + + let response = client + .broadcast_blob_tx(tx, blobs, BroadcastMode::Sync) + .await + .unwrap(); + + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + + let _submitted_tx = client + .get_tx(response.txhash) + .await + .expect("get to be successful"); +} diff --git a/grpc/tests/utils.rs b/grpc/tests/utils.rs new file mode 100644 index 00000000..d5c467ca --- /dev/null +++ b/grpc/tests/utils.rs @@ -0,0 +1,84 @@ +#![cfg(not(target_arch = "wasm32"))] + +use std::{env, fs}; + +use anyhow::Result; +use tonic::metadata::{Ascii, MetadataValue}; +use tonic::service::Interceptor; +use tonic::transport::Channel; +use tonic::{Request, Status}; + +use celestia_grpc::GrpcClient; +use celestia_tendermint::crypto::default::ecdsa_secp256k1::SigningKey; +use celestia_tendermint::public_key::Secp256k1 as VerifyingKey; +use celestia_types::state::Address; + +const CELESTIA_GRPC_URL: &str = "http://localhost:19090"; + +/// [`TestAccount`] stores celestia account credentials and information, for cases where we don't +/// mind jusk keeping the plaintext secret key in memory +#[derive(Debug, Clone)] +pub struct TestAccount { + /// Bech32 `AccountId` of this account + pub address: Address, + /// public key + pub verifying_key: VerifyingKey, + /// private key + pub signing_key: SigningKey, +} + +// +#[derive(Clone)] +pub struct TestAuthInterceptor { + token: Option>, +} + +impl Interceptor for TestAuthInterceptor { + fn call(&mut self, mut request: Request<()>) -> Result, Status> { + if let Some(token) = &self.token { + request + .metadata_mut() + .insert("authorization", token.clone()); + } + Ok(request) + } +} + +impl TestAuthInterceptor { + pub fn new(bearer_token: Option) -> Result { + let token = bearer_token.map(|token| token.parse()).transpose()?; + Ok(Self { token }) + } +} + +pub fn env_or(var_name: &str, or_value: &str) -> String { + env::var(var_name).unwrap_or_else(|_| or_value.to_owned()) +} + +pub async fn new_test_client() -> Result> { + let _ = dotenvy::dotenv(); + let url = env_or("CELESTIA_GRPC_URL", CELESTIA_GRPC_URL); + let grpc_channel = Channel::from_shared(url)?.connect().await?; + + let auth_interceptor = TestAuthInterceptor::new(None)?; + Ok(GrpcClient::new(grpc_channel, auth_interceptor)) +} + +pub fn load_account(path: &str) -> TestAccount { + let account_file = format!("{path}.addr"); + let key_file = format!("{path}.plaintext-key"); + + let account = fs::read_to_string(account_file).expect("file with account name to exists"); + let hex_encoded_key = fs::read_to_string(key_file).expect("file with plaintext key to exists"); + + let signing_key = SigningKey::from_slice( + &hex::decode(hex_encoded_key.trim()).expect("valid hex representation"), + ) + .expect("valid key material"); + + TestAccount { + address: account.trim().parse().expect("valid address"), + verifying_key: *signing_key.verifying_key(), + signing_key, + } +} diff --git a/node-wasm/Cargo.toml b/node-wasm/Cargo.toml index 2fc42e4d..4c39e14e 100644 --- a/node-wasm/Cargo.toml +++ b/node-wasm/Cargo.toml @@ -23,11 +23,11 @@ categories = [ crate-type = ["cdylib", "rlib"] [target.'cfg(target_arch = "wasm32")'.dependencies] -blockstore = { workspace = true } -celestia-tendermint = { workspace = true } -celestia-types = { workspace = true } +blockstore.workspace = true +celestia-tendermint.workspace = true +celestia-types.workspace = true libp2p = { workspace = true, features = ["serde"] } -lumina-node = { workspace = true } +lumina-node.workspace = true anyhow = "1.0.86" console_error_panic_hook = "0.1.7" diff --git a/node/Cargo.toml b/node/Cargo.toml index 87cc294c..ed0910db 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -19,10 +19,10 @@ categories = [ ] [dependencies] -celestia-proto = { workspace = true } -celestia-tendermint = { workspace = true } -celestia-tendermint-proto = { workspace = true } -celestia-types = { workspace = true } +celestia-proto.workspace = true +celestia-tendermint.workspace = true +celestia-tendermint-proto.workspace = true +celestia-types.workspace = true libp2p = { workspace = true, features = [ "autonat", "ping", @@ -32,13 +32,13 @@ libp2p = { workspace = true, features = [ "request-response", "kad", ] } +prost.workspace = true async-trait = "0.1.80" beetswap = "0.4.0" cid = { version = "0.11.1", features = ["serde-codec"] } dashmap = "5.5.3" futures = "0.3.30" -prost = { workspace = true } rand = "0.8.5" serde = { version = "1.0.203", features = ["derive"] } smallvec = { version = "1.13.2", features = [ diff --git a/proto/Cargo.toml b/proto/Cargo.toml index af10a84d..22a9e744 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -14,17 +14,33 @@ keywords = ["blockchain", "celestia", "lumina"] categories = ["encoding", "cryptography::cryptocurrencies"] [dependencies] -celestia-tendermint-proto = { workspace = true } -prost = { workspace = true } -prost-types = "0.12.6" +celestia-tendermint-proto.workspace = true +prost.workspace = true +prost-types.workspace = true serde = { version = "1.0.203", features = ["derive"] } +pbjson = { version = "0.7.0", optional = true } +pbjson-types = { version = "0.7.0", optional = true } +tonic = { version = "0.12.3", optional = true, default-features = false, features = [ + "codegen", "prost" +]} [build-dependencies] -prost-build = "0.12.6" -protox = "0.6.1" +prost-build.workspace = true +prost-types.workspace = true +protox = "0.7.1" +tempfile = { version = "3.13.0", optional = true } +pbjson-build = { version = "0.7.0", optional = true } +tonic-build = { version = "0.12.3", default-features = false, optional = true, features = [ "prost" ]} + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tonic = { version = "0.12.3", optional = true, default-features = false, features = [ "transport" ] } +tonic-build = { version = "0.12.3", optional = true, default-features = false, features = ["transport"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3.42" [dev-dependencies] serde_json = "1.0.117" + +[features] +tonic = [ "dep:tonic", "dep:tonic-build", "dep:pbjson", "dep:pbjson-types", "dep:pbjson-build", "dep:tempfile" ] diff --git a/proto/build.rs b/proto/build.rs index 40c61e55..d8fa4617 100644 --- a/proto/build.rs +++ b/proto/build.rs @@ -1,5 +1,7 @@ //! A build script generating rust types from protobuf definitions. +use prost_types::FileDescriptorSet; + const SERIALIZED: &str = r#"#[derive(::serde::Deserialize, ::serde::Serialize)]"#; const SERIALIZED_DEFAULT: &str = r#"#[derive(::serde::Deserialize, ::serde::Serialize)] #[serde(default)]"#; @@ -63,26 +65,56 @@ static CUSTOM_FIELD_ATTRIBUTES: &[(&str, &str)] = &[ (".shwap.Share", BASE64STRING), ]; +#[rustfmt::skip] +static EXTERN_PATHS: &[(&str, &str)] = &[ + (".tendermint", "::celestia_tendermint_proto::v0_34"), + (".google.protobuf.Timestamp", "::celestia_tendermint_proto::google::protobuf::Timestamp"), + (".google.protobuf.Duration", "::celestia_tendermint_proto::google::protobuf::Duration"), + #[cfg(feature = "tonic")] + (".google.protobuf.Any", "::pbjson_types::Any"), +]; + +const PROTO_FILES: &[&str] = &[ + "vendor/celestia/blob/v1/params.proto", + "vendor/celestia/blob/v1/query.proto", + "vendor/celestia/blob/v1/tx.proto", + "vendor/celestia/core/v1/da/data_availability_header.proto", + "vendor/cosmos/auth/v1beta1/auth.proto", + "vendor/cosmos/auth/v1beta1/query.proto", + "vendor/cosmos/base/abci/v1beta1/abci.proto", + "vendor/cosmos/base/node/v1beta1/query.proto", + "vendor/cosmos/base/tendermint/v1beta1/query.proto", + "vendor/cosmos/base/v1beta1/coin.proto", + "vendor/cosmos/crypto/ed25519/keys.proto", + "vendor/cosmos/crypto/multisig/v1beta1/multisig.proto", + "vendor/cosmos/crypto/secp256k1/keys.proto", + "vendor/cosmos/staking/v1beta1/query.proto", + "vendor/cosmos/tx/v1beta1/service.proto", + "vendor/cosmos/tx/v1beta1/tx.proto", + "vendor/go-header/p2p/pb/header_request.proto", + "vendor/header/pb/extended_header.proto", + "vendor/share/eds/byzantine/pb/share.proto", + "vendor/share/shwap/p2p/bitswap/pb/bitswap.proto", + "vendor/share/shwap/pb/shwap.proto", + "vendor/tendermint/types/types.proto", +]; + +const INCLUDES: &[&str] = &["vendor", "vendor/nmt"]; + fn main() { - let fds = protox::compile( - [ - "vendor/celestia/core/v1/da/data_availability_header.proto", - "vendor/celestia/blob/v1/tx.proto", - "vendor/header/pb/extended_header.proto", - "vendor/share/eds/byzantine/pb/share.proto", - "vendor/share/shwap/pb/shwap.proto", - "vendor/share/shwap/p2p/bitswap/pb/bitswap.proto", - "vendor/cosmos/base/v1beta1/coin.proto", - "vendor/cosmos/base/abci/v1beta1/abci.proto", - "vendor/cosmos/crypto/multisig/v1beta1/multisig.proto", - "vendor/cosmos/staking/v1beta1/query.proto", - "vendor/cosmos/tx/v1beta1/tx.proto", - "vendor/go-header/p2p/pb/header_request.proto", - ], - ["vendor", "vendor/nmt"], - ) - .expect("protox failed to build"); + let fds = protox_compile(); + #[cfg(not(feature = "tonic"))] + prost_build(fds); + #[cfg(feature = "tonic")] + tonic_build(fds) +} + +fn protox_compile() -> FileDescriptorSet { + protox::compile(PROTO_FILES, INCLUDES).expect("protox failed to build") +} +#[cfg(not(feature = "tonic"))] +fn prost_build(fds: FileDescriptorSet) { let mut config = prost_build::Config::new(); for (type_path, attr) in CUSTOM_TYPE_ATTRIBUTES { @@ -93,19 +125,63 @@ fn main() { config.field_attribute(field_path, attr); } + for (proto_path, rust_path) in EXTERN_PATHS { + config.extern_path(proto_path.to_string(), rust_path.to_string()); + } + config .include_file("mod.rs") - .extern_path(".tendermint", "::celestia_tendermint_proto::v0_34") - .extern_path( - ".google.protobuf.Timestamp", - "::celestia_tendermint_proto::google::protobuf::Timestamp", - ) - .extern_path( - ".google.protobuf.Duration", - "::celestia_tendermint_proto::google::protobuf::Duration", - ) // Comments in Google's protobuf are causing issues with cargo-test .disable_comments([".google"]) .compile_fds(fds) .expect("prost failed"); } + +#[cfg(feature = "tonic")] +fn tonic_build(fds: FileDescriptorSet) { + let buf_img = tempfile::NamedTempFile::new() + .expect("should be able to create a temp file to hold the buf image file descriptor set"); + + let mut prost_config = prost_build::Config::new(); + prost_config.enable_type_names(); + + let mut tonic_config = tonic_build::configure() + .include_file("mod.rs") + .build_client(true) + .build_server(false) + .client_mod_attribute(".", "#[cfg(not(target_arch=\"wasm32\"))]") + .use_arc_self(true) + // override prost-types with pbjson-types + .compile_well_known_types(true) + .file_descriptor_set_path(buf_img.path()) + .skip_protoc_run(); + + for (type_path, attr) in CUSTOM_TYPE_ATTRIBUTES { + tonic_config = tonic_config.type_attribute(type_path, attr); + } + for (proto_path, rust_path) in EXTERN_PATHS { + tonic_config = tonic_config.extern_path(proto_path, rust_path); + } + for (field_path, attr) in CUSTOM_FIELD_ATTRIBUTES { + tonic_config = tonic_config.field_attribute(field_path, attr); + } + + tonic_config + .compile_fds_with_config(prost_config, fds) + .expect("should be able to compile protobuf using tonic"); + + let descriptor_set = std::fs::read(buf_img.path()) + .expect("the buf image/descriptor set must exist and be readable at this point"); + + pbjson_build::Builder::new() + .register_descriptors(&descriptor_set) + .unwrap() + .build(&[ + ".celestia_proto", + ".celestia", + ".cosmos", + ".tendermint", + ".google", + ]) + .unwrap(); +} diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 2a5047c6..fc4fec3b 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -1,5 +1,7 @@ #![allow(clippy::all)] #![allow(missing_docs)] +#![allow(rustdoc::invalid_rust_codeblocks)] +#![cfg(not(doctest))] #![doc = include_str!("../README.md")] pub mod serializers; diff --git a/proto/src/serializers/option_any.rs b/proto/src/serializers/option_any.rs index 266cde4e..f266dbb5 100644 --- a/proto/src/serializers/option_any.rs +++ b/proto/src/serializers/option_any.rs @@ -1,6 +1,10 @@ //! [`serde`] serializer for the optional [`Any`]. +#[cfg(feature = "tonic")] +use pbjson_types::Any; +#[cfg(not(feature = "tonic"))] use prost_types::Any; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Deserialize `Option`. @@ -17,7 +21,7 @@ where let any = Option::::deserialize(deserializer)?.map(|def| Any { type_url: def.type_url, - value: def.value, + value: def.value.into(), }); Ok(any) @@ -65,7 +69,7 @@ mod tests { let msg = TxResponse { tx: Some(Any { type_url: "abc".to_string(), - value: vec![1, 2, 3], + value: vec![1, 2, 3].into(), }), }; let json = serde_json::to_string(&msg).unwrap(); @@ -85,7 +89,7 @@ mod tests { serde_json::from_str(r#"{"tx":{"type_url":"abc","value":"AQID"}}"#).unwrap(); let tx = msg.tx.unwrap(); assert_eq!(tx.type_url, "abc"); - assert_eq!(tx.value, &[1, 2, 3]) + assert_eq!(tx.value.as_ref(), [1, 2, 3]) } #[test] diff --git a/proto/vendor/cosmos/auth/v1beta1/auth.proto b/proto/vendor/cosmos/auth/v1beta1/auth.proto new file mode 100644 index 00000000..963c6f15 --- /dev/null +++ b/proto/vendor/cosmos/auth/v1beta1/auth.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; +package cosmos.auth.v1beta1; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/auth/types"; + +// BaseAccount defines a base account type. It contains all the necessary fields +// for basic account functionality. Any custom account type should extend this +// type for additional functionality (e.g. vesting). +message BaseAccount { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.equal) = false; + + option (cosmos_proto.implements_interface) = "AccountI"; + + string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + google.protobuf.Any pub_key = 2 [(gogoproto.jsontag) = "public_key,omitempty"]; + uint64 account_number = 3; + uint64 sequence = 4; +} + +// ModuleAccount defines an account for modules that holds coins on a pool. +message ModuleAccount { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + option (cosmos_proto.implements_interface) = "ModuleAccountI"; + + BaseAccount base_account = 1 [(gogoproto.embed) = true]; + string name = 2; + repeated string permissions = 3; +} + +// Params defines the parameters for the auth module. +message Params { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + uint64 max_memo_characters = 1; + uint64 tx_sig_limit = 2; + uint64 tx_size_cost_per_byte = 3; + uint64 sig_verify_cost_ed25519 = 4 [(gogoproto.customname) = "SigVerifyCostED25519"]; + uint64 sig_verify_cost_secp256k1 = 5 [(gogoproto.customname) = "SigVerifyCostSecp256k1"]; +} diff --git a/proto/vendor/cosmos/auth/v1beta1/genesis.proto b/proto/vendor/cosmos/auth/v1beta1/genesis.proto new file mode 100644 index 00000000..c88b94ee --- /dev/null +++ b/proto/vendor/cosmos/auth/v1beta1/genesis.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +package cosmos.auth.v1beta1; + +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/auth/v1beta1/auth.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/auth/types"; + +// GenesisState defines the auth module's genesis state. +message GenesisState { + // params defines all the paramaters of the module. + Params params = 1 [(gogoproto.nullable) = false]; + + // accounts are the accounts present at genesis. + repeated google.protobuf.Any accounts = 2; +} diff --git a/proto/vendor/cosmos/auth/v1beta1/query.proto b/proto/vendor/cosmos/auth/v1beta1/query.proto new file mode 100644 index 00000000..1775f128 --- /dev/null +++ b/proto/vendor/cosmos/auth/v1beta1/query.proto @@ -0,0 +1,193 @@ +syntax = "proto3"; +package cosmos.auth.v1beta1; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "google/api/annotations.proto"; +import "cosmos/auth/v1beta1/auth.proto"; +import "cosmos_proto/cosmos.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/auth/types"; + +// Query defines the gRPC querier service. +service Query { + // Accounts returns all the existing accounts + // + // Since: cosmos-sdk 0.43 + rpc Accounts(QueryAccountsRequest) returns (QueryAccountsResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/accounts"; + } + + // Account returns account details based on address. + rpc Account(QueryAccountRequest) returns (QueryAccountResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/accounts/{address}"; + } + + // AccountAddressByID returns account address based on account number. + // + // Since: cosmos-sdk 0.46.2 + rpc AccountAddressByID(QueryAccountAddressByIDRequest) returns (QueryAccountAddressByIDResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/address_by_id/{id}"; + } + + // Params queries all parameters. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/params"; + } + + // ModuleAccounts returns all the existing module accounts. + // + // Since: cosmos-sdk 0.46 + rpc ModuleAccounts(QueryModuleAccountsRequest) returns (QueryModuleAccountsResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/module_accounts"; + } + + // ModuleAccountByName returns the module account info by module name + rpc ModuleAccountByName(QueryModuleAccountByNameRequest) returns (QueryModuleAccountByNameResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/module_accounts/{name}"; + } + + // Bech32Prefix queries bech32Prefix + // + // Since: cosmos-sdk 0.46 + rpc Bech32Prefix(Bech32PrefixRequest) returns (Bech32PrefixResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/bech32"; + } + + // AddressBytesToString converts Account Address bytes to string + // + // Since: cosmos-sdk 0.46 + rpc AddressBytesToString(AddressBytesToStringRequest) returns (AddressBytesToStringResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/bech32/{address_bytes}"; + } + + // AddressStringToBytes converts Address string to bytes + // + // Since: cosmos-sdk 0.46 + rpc AddressStringToBytes(AddressStringToBytesRequest) returns (AddressStringToBytesResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/bech32/{address_string}"; + } +} + +// QueryAccountsRequest is the request type for the Query/Accounts RPC method. +// +// Since: cosmos-sdk 0.43 +message QueryAccountsRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryAccountsResponse is the response type for the Query/Accounts RPC method. +// +// Since: cosmos-sdk 0.43 +message QueryAccountsResponse { + // accounts are the existing accounts + repeated google.protobuf.Any accounts = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryAccountRequest is the request type for the Query/Account RPC method. +message QueryAccountRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // address defines the address to query for. + string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} + +// QueryAccountResponse is the response type for the Query/Account RPC method. +message QueryAccountResponse { + // account defines the account of the corresponding address. + google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1 [(gogoproto.nullable) = false]; +} + +// QueryModuleAccountsRequest is the request type for the Query/ModuleAccounts RPC method. +// +// Since: cosmos-sdk 0.46 +message QueryModuleAccountsRequest {} + +// QueryModuleAccountsResponse is the response type for the Query/ModuleAccounts RPC method. +// +// Since: cosmos-sdk 0.46 +message QueryModuleAccountsResponse { + repeated google.protobuf.Any accounts = 1 [(cosmos_proto.accepts_interface) = "ModuleAccountI"]; +} + +// QueryModuleAccountByNameRequest is the request type for the Query/ModuleAccountByName RPC method. +message QueryModuleAccountByNameRequest { + string name = 1; +} + +// QueryModuleAccountByNameResponse is the response type for the Query/ModuleAccountByName RPC method. +message QueryModuleAccountByNameResponse { + google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "ModuleAccountI"]; +} + +// Bech32PrefixRequest is the request type for Bech32Prefix rpc method. +// +// Since: cosmos-sdk 0.46 +message Bech32PrefixRequest {} + +// Bech32PrefixResponse is the response type for Bech32Prefix rpc method. +// +// Since: cosmos-sdk 0.46 +message Bech32PrefixResponse { + string bech32_prefix = 1; +} + +// AddressBytesToStringRequest is the request type for AddressString rpc method. +// +// Since: cosmos-sdk 0.46 +message AddressBytesToStringRequest { + bytes address_bytes = 1; +} + +// AddressBytesToStringResponse is the response type for AddressString rpc method. +// +// Since: cosmos-sdk 0.46 +message AddressBytesToStringResponse { + string address_string = 1; +} + +// AddressStringToBytesRequest is the request type for AccountBytes rpc method. +// +// Since: cosmos-sdk 0.46 +message AddressStringToBytesRequest { + string address_string = 1; +} + +// AddressStringToBytesResponse is the response type for AddressBytes rpc method. +// +// Since: cosmos-sdk 0.46 +message AddressStringToBytesResponse { + bytes address_bytes = 1; +} + +// QueryAccountAddressByIDRequest is the request type for AccountAddressByID rpc method +// +// Since: cosmos-sdk 0.46.2 +message QueryAccountAddressByIDRequest { + // id is the account number of the address to be queried. This field + // should have been an uint64 (like all account numbers), and will be + // updated to uint64 in a future version of the auth query. + int64 id = 1; +} + +// QueryAccountAddressByIDResponse is the response type for AccountAddressByID rpc method +// +// Since: cosmos-sdk 0.46.2 +message QueryAccountAddressByIDResponse { + string account_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index b3f4724e..4b14163a 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -19,9 +19,13 @@ categories = [ ] [dependencies] +celestia-tendermint-proto.workspace = true +celestia-types.workspace = true +celestia-tendermint.workspace = true +prost.workspace = true + async-trait = "0.1.80" -celestia-types = { workspace = true } -futures = "0.3.25" +futures = "0.3.31" jsonrpsee = { version = "0.24.2", features = ["client-core", "macros"] } serde = { version = "1.0.203", features = ["derive"] } thiserror = "1.0.61" @@ -39,10 +43,9 @@ libp2p = { workspace = true, features = [ "noise", "yamux", ] } +nmt-rs.workspace = true anyhow = "1.0.86" dotenvy = "0.15.7" -futures = "0.3.30" -nmt-rs = { workspace = true } rand = "0.8.5" tokio = { version = "1.38.0", features = ["rt", "macros"] } tracing = "0.1.40" diff --git a/rpc/src/state.rs b/rpc/src/state.rs index 2e349fb9..2ab58350 100644 --- a/rpc/src/state.rs +++ b/rpc/src/state.rs @@ -1,6 +1,6 @@ use celestia_types::state::{ AccAddress, Address, Balance, QueryDelegationResponse, QueryRedelegationsResponse, - QueryUnbondingDelegationResponse, TxResponse, Uint, ValAddress, + QueryUnbondingDelegationResponse, RawTxResponse, Uint, ValAddress, }; use celestia_types::{blob::RawBlob, TxConfig}; use jsonrpsee::proc_macros::rpc; @@ -31,7 +31,7 @@ pub trait State { dest: &ValAddress, amount: Uint, config: TxConfig, - ) -> Result; + ) -> Result; /// CancelUnbondingDelegation cancels a user's pending undelegation from a validator. #[method(name = "state.CancelUnbondingDelegation")] @@ -41,7 +41,7 @@ pub trait State { amount: Uint, height: Uint, config: TxConfig, - ) -> Result; + ) -> Result; /// Delegate sends a user's liquid tokens to a validator for delegation. #[method(name = "state.Delegate")] @@ -50,7 +50,7 @@ pub trait State { addr: &ValAddress, amount: Uint, config: TxConfig, - ) -> Result; + ) -> Result; /// IsStopped checks if the Module's context has been stopped. #[method(name = "state.IsStopped")] @@ -84,7 +84,7 @@ pub trait State { &self, blobs: &[RawBlob], config: TxConfig, - ) -> Result; + ) -> Result; /// Transfer sends the given amount of coins from default wallet of the node to the given account address. #[method(name = "state.Transfer")] @@ -93,7 +93,7 @@ pub trait State { to: &AccAddress, amount: Uint, config: TxConfig, - ) -> Result; + ) -> Result; /// Undelegate undelegates a user's delegated tokens, unbonding them from the current validator. #[method(name = "Undelegate")] @@ -102,5 +102,5 @@ pub trait State { addr: &ValAddress, amount: Uint, config: TxConfig, - ) -> Result; + ) -> Result; } diff --git a/tools/update-proto-vendor.sh b/tools/update-proto-vendor.sh index 5aa1b29e..5b850524 100755 --- a/tools/update-proto-vendor.sh +++ b/tools/update-proto-vendor.sh @@ -44,7 +44,7 @@ cp -r ../target/proto-vendor-src/go-header-main/p2p/pb vendor/go-header/p2p rm -rf vendor/cosmos mkdir -p vendor/cosmos -cp -r ../target/proto-vendor-src/cosmos-sdk-release-v0.46.x-celestia/proto/cosmos/{base,staking,crypto,tx} vendor/cosmos +cp -r ../target/proto-vendor-src/cosmos-sdk-release-v0.46.x-celestia/proto/cosmos/{auth,base,staking,crypto,tx} vendor/cosmos rm -rf vendor/cosmos_proto cp -r ../target/proto-vendor-src/cosmos-proto-1.0.0-alpha7/proto/cosmos_proto vendor diff --git a/types/Cargo.toml b/types/Cargo.toml index 9161b80a..c2363c49 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -14,14 +14,17 @@ keywords = ["blockchain", "celestia", "lumina"] categories = ["encoding", "cryptography::cryptocurrencies"] [dependencies] -blockstore = { workspace = true } -celestia-proto = { workspace = true } -celestia-tendermint = { workspace = true, features = ["std", "rust-crypto"] } -celestia-tendermint-proto = { workspace = true } -nmt-rs = { workspace = true } +blockstore.workspace = true +celestia-proto.workspace = true +celestia-tendermint = { workspace = true, features = ["std", "rust-crypto", "secp256k1"] } +celestia-tendermint-proto.workspace = true +nmt-rs.workspace = true +prost.workspace = true +prost-types.workspace = true base64 = "0.22.1" bech32 = "0.11.0" +bitvec = "1.0.1" bytes = "1.6.0" cid = { version = "0.11.1", default-features = false, features = ["std"] } const_format = "0.2.32" @@ -31,7 +34,7 @@ leopard-codec = "0.1.0" libp2p-identity = { version = "0.2.9", optional = true } multiaddr = { version = "0.18.1", optional = true } multihash = "0.19.1" -prost = { workspace = true } +pbjson-types = { version = "0.7.0", optional = true } rand = { version = "0.8.5", optional = true } ruint = { version = "1.12.3", features = ["serde"] } serde = { version = "1.0.203", features = ["derive"] } @@ -60,6 +63,7 @@ default = ["p2p"] p2p = ["dep:libp2p-identity", "dep:multiaddr", "dep:serde_repr"] test-utils = ["dep:ed25519-consensus", "dep:rand"] wasm-bindgen = ["celestia-tendermint/wasm-bindgen"] +tonic = ["dep:pbjson-types", "celestia-proto/tonic"] [package.metadata.docs.rs] features = ["p2p", "test-utils"] @@ -67,3 +71,4 @@ rustdoc-args = ["--cfg", "docsrs"] [package.metadata.cargo-udeps.ignore] development = ["indoc"] +normal = ["prost-types"] # unused if tonic flag is enabled diff --git a/types/src/blob.rs b/types/src/blob.rs index a5954127..e0a63cf2 100644 --- a/types/src/blob.rs +++ b/types/src/blob.rs @@ -5,6 +5,7 @@ use std::iter; use serde::{Deserialize, Serialize}; mod commitment; +mod msg_pay_for_blobs; use crate::consts::appconsts; use crate::consts::appconsts::{subtree_root_threshold, AppVersion}; @@ -12,7 +13,10 @@ use crate::nmt::Namespace; use crate::{bail_validation, Error, Result, Share}; pub use self::commitment::Commitment; +pub use self::msg_pay_for_blobs::MsgPayForBlobs; +pub use celestia_proto::celestia::blob::v1::MsgPayForBlobs as RawMsgPayForBlobs; pub use celestia_tendermint_proto::v0_34::types::Blob as RawBlob; +pub use celestia_tendermint_proto::v0_34::types::BlobTx as RawBlobTx; /// Arbitrary data that can be stored in the network within certain [`Namespace`]. // NOTE: We don't use the `serde(try_from)` pattern for this type @@ -37,6 +41,15 @@ pub struct Blob { pub index: Option, } +/// Params defines the parameters for the blob module. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct BlobParams { + /// Gas cost per blob byte + pub gas_per_blob_byte: u32, + /// Max square size + pub gov_max_square_size: u64, +} + impl Blob { /// Create a new blob with the given data within the [`Namespace`]. /// diff --git a/types/src/blob/msg_pay_for_blobs.rs b/types/src/blob/msg_pay_for_blobs.rs new file mode 100644 index 00000000..2fe76801 --- /dev/null +++ b/types/src/blob/msg_pay_for_blobs.rs @@ -0,0 +1,139 @@ +use serde::{Deserialize, Serialize}; + +use celestia_proto::celestia::blob::v1::MsgPayForBlobs as RawMsgPayForBlobs; +use celestia_tendermint_proto::Protobuf; + +use crate::blob::{Blob, Commitment}; +use crate::nmt::Namespace; +use crate::state::Address; +use crate::{Error, Result}; + +/// MsgPayForBlobs pays for the inclusion of a blob in the block. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MsgPayForBlobs { + /// signer is the bech32 encoded signer address + pub signer: Address, + /// namespaces is a list of namespaces that the blobs are associated with. + pub namespaces: Vec, + /// sizes of the associated blobs + pub blob_sizes: Vec, + /// share_commitments is a list of share commitments (one per blob). + pub share_commitments: Vec, + /// share_versions are the versions of the share format that the blobs + /// associated with this message should use when included in a block. The + /// share_versions specified must match the share_versions used to generate the + /// share_commitment in this message. + pub share_versions: Vec, +} + +impl MsgPayForBlobs { + /// Create a pay for blobs message for the provided Blobs and signer + pub fn new(blobs: &[Blob], signer: Address) -> Result { + let blob_count = blobs.len(); + if blob_count == 0 { + return Err(Error::EmptyBlobList); + } + let mut blob_sizes = Vec::with_capacity(blob_count); + let mut namespaces = Vec::with_capacity(blob_count); + let mut share_commitments = Vec::with_capacity(blob_count); + let mut share_versions = Vec::with_capacity(blob_count); + for blob in blobs { + blob_sizes.push(u32::try_from(blob.data.len()).map_err(|_| Error::BlobTooLarge)?); + namespaces.push(blob.namespace); + share_commitments.push(blob.commitment); + share_versions.push(u32::from(blob.share_version)); + } + + Ok(Self { + signer, + namespaces, + blob_sizes, + share_commitments, + share_versions, + }) + } +} + +#[cfg(feature = "tonic")] +mod tx_body_conversion { + use super::{MsgPayForBlobs, RawMsgPayForBlobs}; + + use std::convert::Infallible; + + use pbjson_types::Any; + use prost::Name; + + use celestia_proto::cosmos::tx::v1beta1::TxBody as RawTxBody; + use celestia_tendermint_proto::Protobuf; + + impl From for RawTxBody { + fn from(msg: MsgPayForBlobs) -> Self { + let msg_pay_for_blobs_value: Result<_, Infallible> = msg.encode_vec(); + let msg_pay_for_blobs_as_any = Any { + type_url: RawMsgPayForBlobs::type_url(), + value: msg_pay_for_blobs_value + .expect("Result to be Infallible") + .into(), + }; + + RawTxBody { + messages: vec![msg_pay_for_blobs_as_any], + ..RawTxBody::default() + } + } + } +} + +impl From for RawMsgPayForBlobs { + fn from(msg: MsgPayForBlobs) -> Self { + let namespaces = msg + .namespaces + .into_iter() + .map(|n| n.as_bytes().to_vec()) + .collect(); + let share_commitments = msg + .share_commitments + .into_iter() + .map(|c| c.0.to_vec()) + .collect(); + + RawMsgPayForBlobs { + signer: msg.signer.to_string(), + namespaces, + blob_sizes: msg.blob_sizes, + share_commitments, + share_versions: msg.share_versions, + } + } +} + +impl TryFrom for MsgPayForBlobs { + type Error = Error; + + fn try_from(msg: RawMsgPayForBlobs) -> Result { + let namespaces = msg + .namespaces + .into_iter() + .map(|n| Namespace::from_raw(&n)) + .collect::>()?; + let share_commitments = msg + .share_commitments + .into_iter() + .map(|c| { + Ok(Commitment( + c.try_into().map_err(|_| Error::InvalidComittmentLength)?, + )) + }) + .collect::>()?; + + Ok(MsgPayForBlobs { + signer: msg.signer.parse()?, + namespaces, + blob_sizes: msg.blob_sizes, + share_commitments, + share_versions: msg.share_versions, + }) + } +} + +impl Protobuf for MsgPayForBlobs {} diff --git a/types/src/error.rs b/types/src/error.rs index 86ad0aa4..c8475241 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -66,6 +66,30 @@ pub enum Error { #[error("Missing shares")] MissingShares, + /// Missing fee field + #[error("Missing fee field")] + MissingFee, + + /// Missing sum field + #[error("Missing sum field")] + MissingSum, + + /// Missing mode info field + #[error("Missing mode info field")] + MissingModeInfo, + + /// Missing bitarray field + #[error("Missing bitarray field")] + MissingBitarray, + + /// Bit array too large + #[error("Bit array to large")] + BitarrayTooLarge, + + /// Malformed CompactBitArray + #[error("CompactBitArray malformed")] + MalformedCompactBitArray, + /// Wrong proof type. #[error("Wrong proof type")] WrongProofType, @@ -117,6 +141,10 @@ pub enum Error { #[error("Invalid proof type: {0}")] InvalidShwapProofType(i32), + /// Could not deserialise Public Key + #[error("Could not deserialize public key")] + InvalidPublicKey, + /// Range proof verification error. #[error("Range proof verification failed: {0:?}")] RangeProofError(nmt_rs::simple_merkle::error::RangeProofError), @@ -168,6 +196,14 @@ pub enum Error { #[error("Invalid balance amount: {0}")] InvalidBalanceAmount(String), + /// Invalid coin amount. + #[error("Invalid coin amount: {0}")] + InvalidCoinAmount(String), + + /// Invalid Public Key + #[error("Invalid Public Key")] + InvalidPublicKeyType(String), + /// Unsupported fraud proof type. #[error("Unsupported fraud proof type: {0}")] UnsupportedFraudProofType(String), @@ -203,6 +239,18 @@ pub enum Error { /// Metadata mismatch between shares in blob. #[error("Metadata mismatch between shares in blob: {0}")] BlobSharesMetadataMismatch(String), + + /// Blob too large, length must fit u32 + #[error("Blob too large")] + BlobTooLarge, + + /// Invalid comittment length + #[error("Invalid committment length")] + InvalidComittmentLength, + + /// Empty blob list provided when creating MsgPayForBlobs + #[error("Empty blob list")] + EmptyBlobList, } impl From for Error { diff --git a/types/src/state.rs b/types/src/state.rs index 582be621..562e679b 100644 --- a/types/src/state.rs +++ b/types/src/state.rs @@ -1,7 +1,9 @@ //! Types and interfaces for accessing Celestia's state-relevant information. mod address; +pub mod auth; mod balance; +mod bit_array; mod query_delegation; mod tx; @@ -10,7 +12,10 @@ pub use self::balance::Balance; pub use self::query_delegation::{ QueryDelegationResponse, QueryRedelegationsResponse, QueryUnbondingDelegationResponse, }; -pub use self::tx::{RawTx, TxResponse}; +pub use self::tx::{ + AuthInfo, Coin, Fee, ModeInfo, RawTx, RawTxBody, RawTxResponse, SignerInfo, Sum, Tx, TxBody, + TxResponse, BOND_DENOM, +}; /// A 256-bit unsigned integer. pub type Uint = ruint::aliases::U256; diff --git a/types/src/state/auth.rs b/types/src/state/auth.rs new file mode 100644 index 00000000..18b9ec6b --- /dev/null +++ b/types/src/state/auth.rs @@ -0,0 +1,143 @@ +//! types related to accounts + +#[cfg(feature = "tonic")] +use pbjson_types::Any; +use prost::Message; +#[cfg(not(feature = "tonic"))] +use prost_types::Any; + +use celestia_proto::cosmos::crypto::ed25519::PubKey as Ed25519PubKey; +use celestia_proto::cosmos::crypto::secp256k1::PubKey as Secp256k1PubKey; +use celestia_tendermint::public_key::PublicKey; +use celestia_tendermint_proto::Protobuf; + +use crate::state::Address; +use crate::Error; + +pub use celestia_proto::cosmos::auth::v1beta1::BaseAccount as RawBaseAccount; +pub use celestia_proto::cosmos::auth::v1beta1::ModuleAccount as RawModuleAccount; +pub use celestia_proto::cosmos::auth::v1beta1::Params as AuthParams; + +const COSMOS_ED25519_PUBKEY: &str = "/cosmos.crypto.ed25519.PubKey"; +const COSMOS_SECP256K1_PUBKEY: &str = "/cosmos.crypto.secp256k1.PubKey"; + +/// [`BaseAccount`] defines a base account type. +/// +/// It contains all the necessary fields for basic account functionality. +/// +/// Any custom account type should extend this type for additional functionality +/// (e.g. vesting). +#[derive(Debug, Clone, PartialEq)] +pub struct BaseAccount { + /// Bech32 `AccountId` of this account. + pub address: Address, + /// Optional `PublicKey` associated with this account. + pub pub_key: Option, + /// `account_number` is the account number of the account in state + pub account_number: u64, + /// Sequence of the account, which describes the number of committed transactions signed by a + /// given address. + pub sequence: u64, +} + +/// [`ModuleAccount`] defines an account for modules that holds coins on a pool. +#[derive(Debug, Clone, PartialEq)] +pub struct ModuleAccount { + /// [`BaseAccount`] specification of this module account. + pub base_account: Option, + /// Name of the module. + pub name: String, + /// Permissions associated with this module account. + pub permissions: Vec, +} + +impl From for RawBaseAccount { + fn from(account: BaseAccount) -> Self { + RawBaseAccount { + address: account.address.to_string(), + pub_key: account.pub_key.map(any_from_public_key), + account_number: account.account_number, + sequence: account.sequence, + } + } +} + +impl TryFrom for BaseAccount { + type Error = Error; + + fn try_from(account: RawBaseAccount) -> Result { + let pub_key = account.pub_key.map(public_key_from_any).transpose()?; + Ok(BaseAccount { + address: account.address.parse()?, + pub_key, + account_number: account.account_number, + sequence: account.sequence, + }) + } +} + +impl From for RawModuleAccount { + fn from(account: ModuleAccount) -> Self { + let base_account = account.base_account.map(BaseAccount::into); + RawModuleAccount { + base_account, + name: account.name, + permissions: account.permissions, + } + } +} + +impl TryFrom for ModuleAccount { + type Error = Error; + + fn try_from(account: RawModuleAccount) -> Result { + let base_account = account + .base_account + .map(RawBaseAccount::try_into) + .transpose()?; + Ok(ModuleAccount { + base_account, + name: account.name, + permissions: account.permissions, + }) + } +} + +fn public_key_from_any(any: Any) -> Result { + match any.type_url.as_ref() { + COSMOS_ED25519_PUBKEY => { + PublicKey::from_raw_ed25519(&Ed25519PubKey::decode(&*any.value)?.key) + } + COSMOS_SECP256K1_PUBKEY => { + PublicKey::from_raw_secp256k1(&Secp256k1PubKey::decode(&*any.value)?.key) + } + other => return Err(Error::InvalidPublicKeyType(other.to_string())), + } + .ok_or(Error::InvalidPublicKey) +} + +fn any_from_public_key(key: PublicKey) -> Any { + match key { + key @ PublicKey::Ed25519(_) => Any { + type_url: COSMOS_ED25519_PUBKEY.to_string(), + value: Ed25519PubKey { + key: key.to_bytes(), + } + .encode_to_vec() + .into(), + }, + key @ PublicKey::Secp256k1(_) => Any { + type_url: COSMOS_SECP256K1_PUBKEY.to_string(), + value: Secp256k1PubKey { + key: key.to_bytes(), + } + .encode_to_vec() + .into(), + }, + _ => unimplemented!("unexpected key type"), + } +} + +impl Protobuf for BaseAccount {} + +impl Protobuf for ModuleAccount {} diff --git a/types/src/state/bit_array.rs b/types/src/state/bit_array.rs new file mode 100644 index 00000000..51021be1 --- /dev/null +++ b/types/src/state/bit_array.rs @@ -0,0 +1,116 @@ +use bitvec::{order::Msb0, vec::BitVec}; + +use celestia_proto::cosmos::crypto::multisig::v1beta1::CompactBitArray; + +use crate::Error; + +#[derive(Debug, Clone, PartialEq)] +pub struct BitVector(pub BitVec); + +impl TryFrom for BitVector { + type Error = Error; + + fn try_from(value: CompactBitArray) -> Result { + let num_bytes = value.elems.len(); + if num_bytes == 0 && value.extra_bits_stored != 0 { + return Err(Error::MalformedCompactBitArray); + } + + let last_byte_bits = match value.extra_bits_stored { + 0 => 8, + n @ 1..=7 => n, + _ => return Err(Error::MalformedCompactBitArray), + } as usize; + let num_bits = num_bytes.saturating_sub(1) * 8 + last_byte_bits; + let mut bit_vec = + BitVec::<_, Msb0>::try_from_vec(value.elems).map_err(|_| Error::BitarrayTooLarge)?; + + bit_vec.truncate(num_bits); + + Ok(BitVector(bit_vec)) + } +} + +impl From for CompactBitArray { + fn from(mut value: BitVector) -> Self { + let num_bits = value.0.len(); + // make sure we don't touch uninitialised memory + value.0.set_uninitialized(false); + let bytes = value.0.into_vec(); + // N mod 8 must fit u32, this is safe + let extra_bits_stored = (num_bits % 8) as u32; + CompactBitArray { + extra_bits_stored, + elems: bytes, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bitvec::prelude::*; + + #[test] + fn test_empty() { + let source = CompactBitArray { + extra_bits_stored: 0, + elems: vec![], + }; + let vec = BitVector::try_from(source.clone()).unwrap(); + assert!(vec.0.is_empty()); + let result = CompactBitArray::from(vec); + assert_eq!(source, result); + } + + #[test] + fn byte_aligned() { + let source = CompactBitArray { + extra_bits_stored: 0, + elems: vec![0b00000001, 0b00000010], + }; + let expected_bits = bits![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0]; + + let vec = BitVector::try_from(source.clone()).unwrap(); + assert_eq!(vec.0.len(), 16); + assert_eq!(vec.0, expected_bits); + + let result = CompactBitArray::from(vec); + assert_eq!(source, result); + } + + // test relies on the fact that we're setting uninitialised part of bitfield to 0, + // which we do anyway for safety + #[test] + fn with_extra_bytes() { + let source = CompactBitArray { + extra_bits_stored: 1, + elems: vec![0b00000000, 0b10000000], + }; + let expected_bits = bits![0, 0, 0, 0, 0, 0, 0, 0, 1]; + + let vec = BitVector::try_from(source.clone()).unwrap(); + assert_eq!(vec.0.len(), 9); + assert_eq!(vec.0, expected_bits); + + let result = CompactBitArray::from(vec); + assert_eq!(source, result); + } + + #[test] + fn malformed() { + let source = CompactBitArray { + extra_bits_stored: 8, + elems: vec![0b00000000], + }; + let error = BitVector::try_from(source.clone()).unwrap_err(); + assert!(matches!(error, Error::MalformedCompactBitArray)); + + let source = CompactBitArray { + extra_bits_stored: 1, + elems: vec![], + }; + let error = BitVector::try_from(source.clone()).unwrap_err(); + assert!(matches!(error, Error::MalformedCompactBitArray)); + } +} diff --git a/types/src/state/tx.rs b/types/src/state/tx.rs index b1f72e4a..a4491538 100644 --- a/types/src/state/tx.rs +++ b/types/src/state/tx.rs @@ -1,17 +1,449 @@ -use celestia_proto::cosmos::base::abci::v1beta1::TxResponse as RawTxResponse; +use celestia_tendermint_proto::Protobuf; +#[cfg(feature = "tonic")] +use pbjson_types::Any; +#[cfg(not(feature = "tonic"))] +use prost_types::Any; use serde::{Deserialize, Serialize}; -/// Raw transaction data. -/// -/// # Note -/// -/// Transaction has no types at this level +use celestia_proto::cosmos::base::abci::v1beta1::AbciMessageLog; +use celestia_tendermint_proto::v0_34::abci::Event; + +use crate::state::bit_array::BitVector; +use crate::Error; +use crate::Height; + +pub use celestia_proto::cosmos::base::abci::v1beta1::TxResponse as RawTxResponse; +pub use celestia_proto::cosmos::base::v1beta1::Coin as RawCoin; +pub use celestia_proto::cosmos::tx::v1beta1::mode_info::Sum as RawSum; +pub use celestia_proto::cosmos::tx::v1beta1::mode_info::{Multi, Single}; +pub use celestia_proto::cosmos::tx::v1beta1::AuthInfo as RawAuthInfo; +pub use celestia_proto::cosmos::tx::v1beta1::Fee as RawFee; +pub use celestia_proto::cosmos::tx::v1beta1::ModeInfo as RawModeInfo; +pub use celestia_proto::cosmos::tx::v1beta1::SignerInfo as RawSignerInfo; +pub use celestia_proto::cosmos::tx::v1beta1::Tx as RawTx; +pub use celestia_proto::cosmos::tx::v1beta1::TxBody as RawTxBody; + +pub type Signature = Vec; + +/// [`BOND_DENOM`] defines the native staking denomination +pub const BOND_DENOM: &str = "utia"; + +/// [`Tx`] is the standard type used for broadcasting transactions. +#[derive(Debug, Clone)] +pub struct Tx { + /// Processable content of the transaction + pub body: TxBody, + + /// Authorization related content of the transaction, specifically signers, signer modes + /// and [`Fee`]. + pub auth_info: AuthInfo, + + /// List of signatures that matches the length and order of [`AuthInfo`]’s `signer_info`s to + /// allow connecting signature meta information like public key and signing mode by position. + /// + /// Signatures are provided as raw bytes so as to support current and future signature types. + /// [`AuthInfo`] should be introspected to determine the signature algorithm used. + pub signatures: Vec, +} + +/// [`TxBody`] of a transaction that all signers sign over. +#[derive(Debug, Clone)] +pub struct TxBody { + /// `messages` is a list of messages to be executed. The required signers of + /// those messages define the number and order of elements in `AuthInfo`'s + /// signer_infos and Tx's signatures. Each required signer address is added to + /// the list only the first time it occurs. + /// + /// By convention, the first required signer (usually from the first message) + /// is referred to as the primary signer and pays the fee for the whole + /// transaction. + pub messages: Vec, + /// `memo` is any arbitrary memo to be added to the transaction. + pub memo: String, + /// `timeout` is the block height after which this transaction will not + /// be processed by the chain + pub timeout_height: Height, + /// `extension_options` are arbitrary options that can be added by chains + /// when the default options are not sufficient. If any of these are present + /// and can't be handled, the transaction will be rejected + pub extension_options: Vec, + /// `extension_options` are arbitrary options that can be added by chains + /// when the default options are not sufficient. If any of these are present + /// and can't be handled, they will be ignored + pub non_critical_extension_options: Vec, +} + +/// Response to a tx query #[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(transparent)] -pub struct RawTx { - #[serde(with = "celestia_tendermint_proto::serializers::bytes::base64string")] - data: Vec, +pub struct TxResponse { + /// The block height + pub height: Height, + + /// The transaction hash. + pub txhash: String, + + /// Namespace for the Code + pub codespace: String, + + /// Response code. + pub code: u32, + + /// Result bytes, if any. + pub data: String, + + /// The output of the application's logger (raw string). May be + /// non-deterministic. + pub raw_log: String, + + /// The output of the application's logger (typed). May be non-deterministic. + pub logs: Vec, + + /// Additional information. May be non-deterministic. + pub info: String, + + /// Amount of gas requested for transaction. + pub gas_wanted: i64, + + /// Amount of gas consumed by transaction. + pub gas_used: i64, + + /// The request transaction bytes. + #[serde(skip)] + // caused by prost_types/pbjson_types::Any conditional compilation, should be + // removed once we align on tendermint::Any + pub tx: Option, + + /// Time of the previous block. For heights > 1, it's the weighted median of + /// the timestamps of the valid votes in the block.LastCommit. For height == 1, + /// it's genesis time. + pub timestamp: String, + + /// Events defines all the events emitted by processing a transaction. Note, + /// these events include those emitted by processing all the messages and those + /// emitted from the ante. Whereas Logs contains the events, with + /// additional metadata, emitted only by processing the messages. + pub events: Vec, +} + +/// [`AuthInfo`] describes the fee and signer modes that are used to sign a transaction. +#[derive(Debug, Clone)] +pub struct AuthInfo { + /// Defines the signing modes for the required signers. + /// + /// The number and order of elements must match the required signers from transaction + /// [`TxBody`]’s messages. The first element is the primary signer and the one + /// which pays the [`Fee`]. + pub signer_infos: Vec, + /// [`Fee`] and gas limit for the transaction. + /// + /// The first signer is the primary signer and the one which pays the fee. + /// The fee can be calculated based on the cost of evaluating the body and doing signature + /// verification of the signers. This can be estimated via simulation. + pub fee: Fee, +} + +/// SignerInfo describes the public key and signing mode of a single top-level +/// signer. +#[derive(Debug, Clone, PartialEq)] +pub struct SignerInfo { + /// public_key is the public key of the signer. It is optional for accounts + /// that already exist in state. If unset, the verifier can use the required \ + /// signer address for this position and lookup the public key. + pub public_key: Option, + /// mode_info describes the signing mode of the signer and is a nested + /// structure to support nested multisig pubkey's + pub mode_info: ModeInfo, + /// sequence is the sequence of the account, which describes the + /// number of committed transactions signed by a given address. It is used to + /// prevent replay attacks. + pub sequence: u64, +} + +/// ModeInfo describes the signing mode of a single or nested multisig signer. +#[derive(Debug, Clone, PartialEq)] +pub struct ModeInfo { + /// sum is the oneof that specifies whether this represents a single or nested + /// multisig signer + pub sum: Sum, +} + +/// sum is the oneof that specifies whether this represents a single or nested +/// multisig signer +#[derive(Debug, Clone, PartialEq)] +pub enum Sum { + /// Single is the mode info for a single signer. It is structured as a message + /// to allow for additional fields such as locale for SIGN_MODE_TEXTUAL in the + /// future + Single { + /// mode is the signing mode of the single signer + mode: i32, + }, + /// Multi is the mode info for a multisig public key + Multi { + /// bitarray specifies which keys within the multisig are signing + bitarray: BitVector, + /// mode_infos is the corresponding modes of the signers of the multisig + /// which could include nested multisig public keys + mode_infos: Vec, + }, +} + +/// Fee includes the amount of coins paid in fees and the maximum +/// gas to be used by the transaction. The ratio yields an effective "gasprice", +/// which must be above some miminum to be accepted into the mempool. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Fee { + /// amount is the amount of coins to be paid as a fee + pub amount: Vec, + /// gas_limit is the maximum gas that can be used in transaction processing + /// before an out of gas error occurs + pub gas_limit: u64, + /// if unset, the first signer is responsible for paying the fees. If set, the specified account must pay the fees. + /// the payer must be a tx signer (and thus have signed this field in AuthInfo). + /// setting this field does *not* change the ordering of required signers for the transaction. + pub payer: String, + /// if set, the fee payer (either the first signer or the value of the payer field) requests that a fee grant be used + /// to pay fees instead of the fee payer's own balance. If an appropriate fee grant does not exist or the chain does + /// not support fee grants, this will fail + pub granter: String, +} + +/// Coin defines a token with a denomination and an amount. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Coin { + /// Coin denomination + pub denom: String, + /// Coin amount + pub amount: u64, +} + +impl Fee { + /// Create [`Fee`] struct with provided number of utia and gas limit, + /// without setting custom [`Fee::payer`] or [`Fee::granter`] fields, + /// which means first tx signer is responsible for paying. + pub fn new(utia_fee: u64, gas_limit: u64) -> Self { + Fee { + amount: vec![Coin { + denom: BOND_DENOM.to_string(), + amount: utia_fee, + }], + gas_limit, + ..Default::default() + } + } +} + +impl TryFrom for TxBody { + type Error = Error; + + fn try_from(value: RawTxBody) -> Result { + Ok(TxBody { + messages: value.messages, + memo: value.memo, + timeout_height: value.timeout_height.try_into()?, + extension_options: value.extension_options, + non_critical_extension_options: value.non_critical_extension_options, + }) + } +} + +impl From for RawTxBody { + fn from(value: TxBody) -> Self { + RawTxBody { + messages: value.messages, + memo: value.memo, + timeout_height: value.timeout_height.into(), + extension_options: value.extension_options, + non_critical_extension_options: value.non_critical_extension_options, + } + } +} + +impl TryFrom for AuthInfo { + type Error = Error; + + fn try_from(value: RawAuthInfo) -> Result { + let signer_infos = value + .signer_infos + .into_iter() + .map(TryFrom::try_from) + .collect::>()?; + Ok(AuthInfo { + signer_infos, + fee: value.fee.ok_or(Error::MissingFee)?.try_into()?, + }) + } +} + +impl From for RawAuthInfo { + fn from(value: AuthInfo) -> Self { + let signer_infos = value.signer_infos.into_iter().map(Into::into).collect(); + #[allow(deprecated)] // tip is deprecated + RawAuthInfo { + signer_infos, + fee: Some(value.fee.into()), + tip: None, + } + } +} + +impl TryFrom for TxResponse { + type Error = Error; + + fn try_from(response: RawTxResponse) -> Result { + Ok(TxResponse { + height: response.height.try_into()?, + txhash: response.txhash, + codespace: response.codespace, + code: response.code, + data: response.data, + raw_log: response.raw_log, + logs: response.logs, + info: response.info, + gas_wanted: response.gas_wanted, + gas_used: response.gas_used, + tx: response.tx, + timestamp: response.timestamp, + events: response.events, + }) + } +} + +impl TryFrom for SignerInfo { + type Error = Error; + + fn try_from(value: RawSignerInfo) -> Result { + Ok(SignerInfo { + public_key: value.public_key, + mode_info: value.mode_info.ok_or(Error::MissingModeInfo)?.try_into()?, + sequence: value.sequence, + }) + } +} + +impl From for RawSignerInfo { + fn from(value: SignerInfo) -> Self { + RawSignerInfo { + public_key: value.public_key, + mode_info: Some(value.mode_info.into()), + sequence: value.sequence, + } + } +} + +impl TryFrom for Fee { + type Error = Error; + + fn try_from(value: RawFee) -> Result { + let amount = value + .amount + .into_iter() + .map(TryInto::try_into) + .collect::>()?; + Ok(Fee { + amount, + gas_limit: value.gas_limit, + payer: value.payer, + granter: value.granter, + }) + } +} + +impl From for RawFee { + fn from(value: Fee) -> Self { + let amount = value.amount.into_iter().map(Into::into).collect(); + RawFee { + amount, + gas_limit: value.gas_limit, + payer: value.payer, + granter: value.granter, + } + } +} + +impl TryFrom for ModeInfo { + type Error = Error; + + fn try_from(value: RawModeInfo) -> Result { + Ok(ModeInfo { + sum: value.sum.ok_or(Error::MissingSum)?.try_into()?, + }) + } +} + +impl From for RawModeInfo { + fn from(value: ModeInfo) -> Self { + RawModeInfo { + sum: Some(value.sum.into()), + } + } +} + +impl TryFrom for Sum { + type Error = Error; + + fn try_from(value: RawSum) -> Result { + let sum = match value { + RawSum::Single(Single { mode }) => Sum::Single { mode }, + RawSum::Multi(Multi { + bitarray, + mode_infos, + }) => { + let bitarray = bitarray.ok_or(Error::MissingBitarray)?.try_into()?; + let mode_infos = mode_infos + .into_iter() + .map(TryInto::try_into) + .collect::>()?; + Sum::Multi { + bitarray, + mode_infos, + } + } + }; + Ok(sum) + } +} + +impl From for RawSum { + fn from(value: Sum) -> Self { + match value { + Sum::Single { mode } => RawSum::Single(Single { mode }), + Sum::Multi { + bitarray, + mode_infos, + } => { + let mode_infos = mode_infos.into_iter().map(Into::into).collect(); + RawSum::Multi(Multi { + bitarray: Some(bitarray.into()), + mode_infos, + }) + } + } + } +} + +impl From for RawCoin { + fn from(value: Coin) -> Self { + RawCoin { + denom: value.denom, + amount: value.amount.to_string(), + } + } +} + +impl TryFrom for Coin { + type Error = Error; + + fn try_from(value: RawCoin) -> Result { + Ok(Coin { + denom: value.denom, + amount: value + .amount + .parse() + .map_err(|_| Error::InvalidCoinAmount(value.amount))?, + }) + } } -/// Raw transaction response. -pub type TxResponse = RawTxResponse; +impl Protobuf for TxBody {} +impl Protobuf for AuthInfo {}