From 6e33eb52502a77aa16022c96cff449a6c3478748 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Mon, 9 Dec 2024 15:23:55 +0100 Subject: [PATCH 1/2] chore: copy protobuf compiler tool from mono repo This patch copies the cnidarium related parts of the penumbra protobuf compiler tool [1]. Having this tool as part of the cnidarium repo is necessary to update to newer versions of ibc-types, ibc-proto, prost, tonic, and ics23 because these are also targeted by the code generator. This patch contains no logic (such as CI workflows) to maintain parity with the penumbra mono repo. This work is left to a follow-up PR. 1: https://github.com/penumbra-zone/penumbra/tree/159f48489bc8deb654551402fdce61108409c524/tools/proto-compiler --- .../penumbra/cnidarium/v1/cnidarium.proto | 116 +++++++++ .../cosmos/ics23/v1/proofs.proto | 234 ++++++++++++++++++ proto/rust-vendored/gogoproto/gogo.proto | 145 +++++++++++ .../ibc/core/commitment/v1/commitment.proto | 41 +++ src/gen/proto_descriptor.bin.no_lfs | Bin 99489 -> 102002 bytes tools/proto-compiler/Cargo.toml | 18 ++ tools/proto-compiler/README.md | 6 + tools/proto-compiler/src/main.rs | 55 ++++ 8 files changed, 615 insertions(+) create mode 100644 proto/penumbra/penumbra/cnidarium/v1/cnidarium.proto create mode 100644 proto/rust-vendored/cosmos/ics23/v1/proofs.proto create mode 100644 proto/rust-vendored/gogoproto/gogo.proto create mode 100644 proto/rust-vendored/ibc/core/commitment/v1/commitment.proto create mode 100644 tools/proto-compiler/Cargo.toml create mode 100644 tools/proto-compiler/README.md create mode 100644 tools/proto-compiler/src/main.rs diff --git a/proto/penumbra/penumbra/cnidarium/v1/cnidarium.proto b/proto/penumbra/penumbra/cnidarium/v1/cnidarium.proto new file mode 100644 index 0000000..87ecd84 --- /dev/null +++ b/proto/penumbra/penumbra/cnidarium/v1/cnidarium.proto @@ -0,0 +1,116 @@ +syntax = "proto3"; + +package penumbra.cnidarium.v1; + +import "ibc/core/commitment/v1/commitment.proto"; + +service QueryService { + // General-purpose key-value state query API, that can be used to query + // arbitrary keys in the JMT storage. + rpc KeyValue(KeyValueRequest) returns (KeyValueResponse); + + // General-purpose key-value state query API, that can be used to query + // arbitrary keys in the non-verifiable storage. + rpc NonVerifiableKeyValue(NonVerifiableKeyValueRequest) returns (NonVerifiableKeyValueResponse); + + // General-purpose prefixed key-value state query API, that can be used to query + // arbitrary prefixes in the JMT storage. + rpc PrefixValue(PrefixValueRequest) returns (stream PrefixValueResponse); + + // Subscribes to a stream of key-value updates, with regex filtering on keys. + rpc Watch(WatchRequest) returns (stream WatchResponse); +} + +// Performs a key-value query against the nonverifiable storage, +// using a byte-encoded key. +message NonVerifiableKeyValueRequest { + message Key { + bytes inner = 1; + } + + Key key = 1; +} + +message NonVerifiableKeyValueResponse { + message Value { + bytes value = 1; + } + // The value corresponding to the specified key, if it was found. + Value value = 1; +} + +// Performs a key-value query against the JMT, either by key or by key hash. +// +// Proofs are only supported by key. +message KeyValueRequest { + // If set, the key to fetch from storage. + string key = 2; + // whether to return a proof + bool proof = 3; +} + +message KeyValueResponse { + message Value { + bytes value = 1; + } + // The value corresponding to the specified key, if it was found. + Value value = 1; + // A proof of existence or non-existence. + .ibc.core.commitment.v1.MerkleProof proof = 2; +} + +// Performs a prefixed key-value query, by string prefix. +message PrefixValueRequest { + // The prefix to fetch subkeys from storage. + string prefix = 2; +} + +message PrefixValueResponse { + string key = 1; + bytes value = 2; +} + +// Requests a stream of new key-value pairs that have been committed to the state. +message WatchRequest { + // A regex for keys in the verifiable storage. + // + // Only key-value updates whose keys match this regex will be returned. + // Note that the empty string matches all keys. + // To exclude all keys, use the regex "$^", which matches no strings. + string key_regex = 1; + // A regex for keys in the nonverifiable storage. + // + // Only key-value updates whose keys match this regex will be returned. + // Note that the empty string matches all keys. + // To exclude all keys, use the regex "$^", which matches no strings. + string nv_key_regex = 2; +} + +// A key-value pair that has been committed to the state. +message WatchResponse { + // Elements of the verifiable storage have string keys. + message KeyValue { + string key = 1; + bytes value = 2; + // If set to true, the key-value pair was deleted. + // This allows distinguishing a deleted key-value pair from a key-value pair whose value is empty. + bool deleted = 3; + } + // Elements of the nonverifiable storage have byte keys. + message NvKeyValue { + bytes key = 1; + bytes value = 2; + // If set to true, the key-value pair was deleted. + // This allows distinguishing a deleted key-value pair from a key-value pair whose value is empty. + bool deleted = 3; + } + + // The state version the key-value pair was committed at. + uint64 version = 1; + + // The entry that was committed. + oneof entry { + KeyValue kv = 5; + NvKeyValue nv_kv = 6; + } +} diff --git a/proto/rust-vendored/cosmos/ics23/v1/proofs.proto b/proto/rust-vendored/cosmos/ics23/v1/proofs.proto new file mode 100644 index 0000000..0e75dfb --- /dev/null +++ b/proto/rust-vendored/cosmos/ics23/v1/proofs.proto @@ -0,0 +1,234 @@ +syntax = "proto3"; + +package cosmos.ics23.v1; + +option go_package = "github.com/cosmos/ics23/go;ics23"; + +enum HashOp { + // NO_HASH is the default if no data passed. Note this is an illegal argument some places. + NO_HASH = 0; + SHA256 = 1; + SHA512 = 2; + KECCAK = 3; + RIPEMD160 = 4; + BITCOIN = 5; // ripemd160(sha256(x)) + SHA512_256 = 6; +} + +/** +LengthOp defines how to process the key and value of the LeafOp +to include length information. After encoding the length with the given +algorithm, the length will be prepended to the key and value bytes. +(Each one with it's own encoded length) +*/ +enum LengthOp { + // NO_PREFIX don't include any length info + NO_PREFIX = 0; + // VAR_PROTO uses protobuf (and go-amino) varint encoding of the length + VAR_PROTO = 1; + // VAR_RLP uses rlp int encoding of the length + VAR_RLP = 2; + // FIXED32_BIG uses big-endian encoding of the length as a 32 bit integer + FIXED32_BIG = 3; + // FIXED32_LITTLE uses little-endian encoding of the length as a 32 bit integer + FIXED32_LITTLE = 4; + // FIXED64_BIG uses big-endian encoding of the length as a 64 bit integer + FIXED64_BIG = 5; + // FIXED64_LITTLE uses little-endian encoding of the length as a 64 bit integer + FIXED64_LITTLE = 6; + // REQUIRE_32_BYTES is like NONE, but will fail if the input is not exactly 32 bytes (sha256 output) + REQUIRE_32_BYTES = 7; + // REQUIRE_64_BYTES is like NONE, but will fail if the input is not exactly 64 bytes (sha512 output) + REQUIRE_64_BYTES = 8; +} + +/** +ExistenceProof takes a key and a value and a set of steps to perform on it. +The result of peforming all these steps will provide a "root hash", which can +be compared to the value in a header. + +Since it is computationally infeasible to produce a hash collission for any of the used +cryptographic hash functions, if someone can provide a series of operations to transform +a given key and value into a root hash that matches some trusted root, these key and values +must be in the referenced merkle tree. + +The only possible issue is maliablity in LeafOp, such as providing extra prefix data, +which should be controlled by a spec. Eg. with lengthOp as NONE, + prefix = FOO, key = BAR, value = CHOICE +and + prefix = F, key = OOBAR, value = CHOICE +would produce the same value. + +With LengthOp this is tricker but not impossible. Which is why the "leafPrefixEqual" field +in the ProofSpec is valuable to prevent this mutability. And why all trees should +length-prefix the data before hashing it. +*/ +message ExistenceProof { + bytes key = 1; + bytes value = 2; + LeafOp leaf = 3; + repeated InnerOp path = 4; +} + +/* +NonExistenceProof takes a proof of two neighbors, one left of the desired key, +one right of the desired key. If both proofs are valid AND they are neighbors, +then there is no valid proof for the given key. +*/ +message NonExistenceProof { + bytes key = 1; // TODO: remove this as unnecessary??? we prove a range + ExistenceProof left = 2; + ExistenceProof right = 3; +} + +/* +CommitmentProof is either an ExistenceProof or a NonExistenceProof, or a Batch of such messages +*/ +message CommitmentProof { + oneof proof { + ExistenceProof exist = 1; + NonExistenceProof nonexist = 2; + BatchProof batch = 3; + CompressedBatchProof compressed = 4; + } +} + +/** +LeafOp represents the raw key-value data we wish to prove, and +must be flexible to represent the internal transformation from +the original key-value pairs into the basis hash, for many existing +merkle trees. + +key and value are passed in. So that the signature of this operation is: + leafOp(key, value) -> output + +To process this, first prehash the keys and values if needed (ANY means no hash in this case): + hkey = prehashKey(key) + hvalue = prehashValue(value) + +Then combine the bytes, and hash it + output = hash(prefix || length(hkey) || hkey || length(hvalue) || hvalue) +*/ +message LeafOp { + HashOp hash = 1; + HashOp prehash_key = 2; + HashOp prehash_value = 3; + LengthOp length = 4; + // prefix is a fixed bytes that may optionally be included at the beginning to differentiate + // a leaf node from an inner node. + bytes prefix = 5; +} + +/** +InnerOp represents a merkle-proof step that is not a leaf. +It represents concatenating two children and hashing them to provide the next result. + +The result of the previous step is passed in, so the signature of this op is: + innerOp(child) -> output + +The result of applying InnerOp should be: + output = op.hash(op.prefix || child || op.suffix) + + where the || operator is concatenation of binary data, +and child is the result of hashing all the tree below this step. + +Any special data, like prepending child with the length, or prepending the entire operation with +some value to differentiate from leaf nodes, should be included in prefix and suffix. +If either of prefix or suffix is empty, we just treat it as an empty string +*/ +message InnerOp { + HashOp hash = 1; + bytes prefix = 2; + bytes suffix = 3; +} + +/** +ProofSpec defines what the expected parameters are for a given proof type. +This can be stored in the client and used to validate any incoming proofs. + + verify(ProofSpec, Proof) -> Proof | Error + +As demonstrated in tests, if we don't fix the algorithm used to calculate the +LeafHash for a given tree, there are many possible key-value pairs that can +generate a given hash (by interpretting the preimage differently). +We need this for proper security, requires client knows a priori what +tree format server uses. But not in code, rather a configuration object. +*/ +message ProofSpec { + // any field in the ExistenceProof must be the same as in this spec. + // except Prefix, which is just the first bytes of prefix (spec can be longer) + LeafOp leaf_spec = 1; + InnerSpec inner_spec = 2; + // max_depth (if > 0) is the maximum number of InnerOps allowed (mainly for fixed-depth tries) + int32 max_depth = 3; + // min_depth (if > 0) is the minimum number of InnerOps allowed (mainly for fixed-depth tries) + int32 min_depth = 4; +} + +/* +InnerSpec contains all store-specific structure info to determine if two proofs from a +given store are neighbors. + +This enables: + + isLeftMost(spec: InnerSpec, op: InnerOp) + isRightMost(spec: InnerSpec, op: InnerOp) + isLeftNeighbor(spec: InnerSpec, left: InnerOp, right: InnerOp) +*/ +message InnerSpec { + // Child order is the ordering of the children node, must count from 0 + // iavl tree is [0, 1] (left then right) + // merk is [0, 2, 1] (left, right, here) + repeated int32 child_order = 1; + int32 child_size = 2; + int32 min_prefix_length = 3; + int32 max_prefix_length = 4; + // empty child is the prehash image that is used when one child is nil (eg. 20 bytes of 0) + bytes empty_child = 5; + // hash is the algorithm that must be used for each InnerOp + HashOp hash = 6; +} + +/* +BatchProof is a group of multiple proof types than can be compressed +*/ +message BatchProof { + repeated BatchEntry entries = 1; +} + +// Use BatchEntry not CommitmentProof, to avoid recursion +message BatchEntry { + oneof proof { + ExistenceProof exist = 1; + NonExistenceProof nonexist = 2; + } +} + +/****** all items here are compressed forms *******/ + +message CompressedBatchProof { + repeated CompressedBatchEntry entries = 1; + repeated InnerOp lookup_inners = 2; +} + +// Use BatchEntry not CommitmentProof, to avoid recursion +message CompressedBatchEntry { + oneof proof { + CompressedExistenceProof exist = 1; + CompressedNonExistenceProof nonexist = 2; + } +} + +message CompressedExistenceProof { + bytes key = 1; + bytes value = 2; + LeafOp leaf = 3; + // these are indexes into the lookup_inners table in CompressedBatchProof + repeated int32 path = 4; +} + +message CompressedNonExistenceProof { + bytes key = 1; // TODO: remove this as unnecessary??? we prove a range + CompressedExistenceProof left = 2; + CompressedExistenceProof right = 3; +} diff --git a/proto/rust-vendored/gogoproto/gogo.proto b/proto/rust-vendored/gogoproto/gogo.proto new file mode 100644 index 0000000..974b36a --- /dev/null +++ b/proto/rust-vendored/gogoproto/gogo.proto @@ -0,0 +1,145 @@ +// Protocol Buffers for Go with Gadgets +// +// Copyright (c) 2013, The GoGo Authors. All rights reserved. +// http://github.com/cosmos/gogoproto +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; +package gogoproto; + +import "google/protobuf/descriptor.proto"; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "GoGoProtos"; +option go_package = "github.com/cosmos/gogoproto/gogoproto"; + +extend google.protobuf.EnumOptions { + optional bool goproto_enum_prefix = 62001; + optional bool goproto_enum_stringer = 62021; + optional bool enum_stringer = 62022; + optional string enum_customname = 62023; + optional bool enumdecl = 62024; +} + +extend google.protobuf.EnumValueOptions { + optional string enumvalue_customname = 66001; +} + +extend google.protobuf.FileOptions { + optional bool goproto_getters_all = 63001; + optional bool goproto_enum_prefix_all = 63002; + optional bool goproto_stringer_all = 63003; + optional bool verbose_equal_all = 63004; + optional bool face_all = 63005; + optional bool gostring_all = 63006; + optional bool populate_all = 63007; + optional bool stringer_all = 63008; + optional bool onlyone_all = 63009; + + optional bool equal_all = 63013; + optional bool description_all = 63014; + optional bool testgen_all = 63015; + optional bool benchgen_all = 63016; + optional bool marshaler_all = 63017; + optional bool unmarshaler_all = 63018; + optional bool stable_marshaler_all = 63019; + + optional bool sizer_all = 63020; + + optional bool goproto_enum_stringer_all = 63021; + optional bool enum_stringer_all = 63022; + + optional bool unsafe_marshaler_all = 63023; + optional bool unsafe_unmarshaler_all = 63024; + + optional bool goproto_extensions_map_all = 63025; + optional bool goproto_unrecognized_all = 63026; + optional bool gogoproto_import = 63027; + optional bool protosizer_all = 63028; + optional bool compare_all = 63029; + optional bool typedecl_all = 63030; + optional bool enumdecl_all = 63031; + + optional bool goproto_registration = 63032; + optional bool messagename_all = 63033; + + optional bool goproto_sizecache_all = 63034; + optional bool goproto_unkeyed_all = 63035; +} + +extend google.protobuf.MessageOptions { + optional bool goproto_getters = 64001; + optional bool goproto_stringer = 64003; + optional bool verbose_equal = 64004; + optional bool face = 64005; + optional bool gostring = 64006; + optional bool populate = 64007; + optional bool stringer = 67008; + optional bool onlyone = 64009; + + optional bool equal = 64013; + optional bool description = 64014; + optional bool testgen = 64015; + optional bool benchgen = 64016; + optional bool marshaler = 64017; + optional bool unmarshaler = 64018; + optional bool stable_marshaler = 64019; + + optional bool sizer = 64020; + + optional bool unsafe_marshaler = 64023; + optional bool unsafe_unmarshaler = 64024; + + optional bool goproto_extensions_map = 64025; + optional bool goproto_unrecognized = 64026; + + optional bool protosizer = 64028; + optional bool compare = 64029; + + optional bool typedecl = 64030; + + optional bool messagename = 64033; + + optional bool goproto_sizecache = 64034; + optional bool goproto_unkeyed = 64035; +} + +extend google.protobuf.FieldOptions { + optional bool nullable = 65001; + optional bool embed = 65002; + optional string customtype = 65003; + optional string customname = 65004; + optional string jsontag = 65005; + optional string moretags = 65006; + optional string casttype = 65007; + optional string castkey = 65008; + optional string castvalue = 65009; + + optional bool stdtime = 65010; + optional bool stdduration = 65011; + optional bool wktpointer = 65012; + + optional string castrepeated = 65013; +} diff --git a/proto/rust-vendored/ibc/core/commitment/v1/commitment.proto b/proto/rust-vendored/ibc/core/commitment/v1/commitment.proto new file mode 100644 index 0000000..bbad400 --- /dev/null +++ b/proto/rust-vendored/ibc/core/commitment/v1/commitment.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package ibc.core.commitment.v1; + +option go_package = "github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/ics23/v1/proofs.proto"; + +// MerkleRoot defines a merkle root hash. +// In the Cosmos SDK, the AppHash of a block header becomes the root. +message MerkleRoot { + option (gogoproto.goproto_getters) = false; + + bytes hash = 1; +} + +// MerklePrefix is merkle path prefixed to the key. +// The constructed key from the Path and the key will be append(Path.KeyPath, +// append(Path.KeyPrefix, key...)) +message MerklePrefix { + bytes key_prefix = 1; +} + +// MerklePath is the path used to verify commitment proofs, which can be an +// arbitrary structured object (defined by a commitment type). +// MerklePath is represented from root-to-leaf +message MerklePath { + option (gogoproto.goproto_stringer) = false; + + repeated string key_path = 1; +} + +// MerkleProof is a wrapper type over a chain of CommitmentProofs. +// It demonstrates membership or non-membership for an element or set of +// elements, verifiable in conjunction with a known commitment root. Proofs +// should be succinct. +// MerkleProofs are ordered from leaf-to-root +message MerkleProof { + repeated cosmos.ics23.v1.CommitmentProof proofs = 1; +} diff --git a/src/gen/proto_descriptor.bin.no_lfs b/src/gen/proto_descriptor.bin.no_lfs index 6e23fc5c3fa90b2ae6af49ab1a84a133ac4ac9bb..f41da7426e0409c58117d4e9d6a186a1632aff36 100644 GIT binary patch delta 23732 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 delta 21358 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 diff --git a/tools/proto-compiler/Cargo.toml b/tools/proto-compiler/Cargo.toml new file mode 100644 index 0000000..43dc1ca --- /dev/null +++ b/tools/proto-compiler/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "penumbra-proto-compiler" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1" +ibc-proto = { version = "0.40.0" } +ics23 = "0.11.3" +pbjson = "0.6" +pbjson-build = "0.6" +pbjson-types = "0.6" +prost = "0.12.3" +prost-build = "0.12.3" +prost-types = "0.12" +tempfile = "3" +tonic-build = { version = "0.10.0", features = ["cleanup-markdown"] } diff --git a/tools/proto-compiler/README.md b/tools/proto-compiler/README.md new file mode 100644 index 0000000..39b738a --- /dev/null +++ b/tools/proto-compiler/README.md @@ -0,0 +1,6 @@ +## How to compile fresh proto structs + +* `cargo run` in the compiler folder. + +The resultant structs will be created in the `proto/src/prost` folder. +Build the `tendermint-proto` crate. diff --git a/tools/proto-compiler/src/main.rs b/tools/proto-compiler/src/main.rs new file mode 100644 index 0000000..53155f5 --- /dev/null +++ b/tools/proto-compiler/src/main.rs @@ -0,0 +1,55 @@ +use std::path::PathBuf; + +fn main() -> anyhow::Result<()> { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + println!("root: {}", root.display()); + + let target_dir = root.join("..").join("..").join("src").join("gen"); + println!("target_dir: {}", target_dir.display()); + + // https://github.com/penumbra-zone/penumbra/issues/3038#issuecomment-1722534133 + // Using the "no_lfs" suffix prevents matching a catch-all LFS rule. + let descriptor_file_name = "proto_descriptor.bin.no_lfs"; + + // prost_build::Config isn't Clone, so we need to make two. + let mut config = prost_build::Config::new(); + + config.compile_well_known_types(); + // As recommended in pbjson_types docs. + config.extern_path(".google.protobuf", "::pbjson_types"); + // NOTE: we need this because the rust module that defines the IBC types is external, and not + // part of this crate. + // See https://docs.rs/prost-build/0.5.0/prost_build/struct.Config.html#method.extern_path + config.extern_path(".ibc", "::ibc_proto::ibc"); + // TODO: which of these is the right path? + config.extern_path(".ics23", "::ics23"); + config.extern_path(".cosmos.ics23", "::ics23"); + + config + .out_dir(&target_dir) + .file_descriptor_set_path(&target_dir.join(descriptor_file_name)) + .enable_type_names(); + + let rpc_doc_attr = r#"#[cfg(feature = "rpc")]"#; + + tonic_build::configure() + .out_dir(&target_dir) + .emit_rerun_if_changed(false) + .server_mod_attribute(".", rpc_doc_attr) + .client_mod_attribute(".", rpc_doc_attr) + .compile_with_config( + config, + &["../../proto/penumbra/penumbra/cnidarium/v1/cnidarium.proto"], + &["../../proto/penumbra/", "../../proto/rust-vendored/"], + )?; + // Finally, build pbjson Serialize, Deserialize impls: + let descriptor_set = std::fs::read(target_dir.join(descriptor_file_name))?; + + pbjson_build::Builder::new() + .register_descriptors(&descriptor_set)? + .ignore_unknown_fields() + .out_dir(&target_dir) + .build(&[".penumbra"])?; + + Ok(()) +} From e10f7db39d51d2dc742062b9c4ea467763f2495c Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Mon, 9 Dec 2024 15:32:27 +0100 Subject: [PATCH 2/2] update readme --- tools/proto-compiler/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/proto-compiler/README.md b/tools/proto-compiler/README.md index 39b738a..5d20d17 100644 --- a/tools/proto-compiler/README.md +++ b/tools/proto-compiler/README.md @@ -2,5 +2,4 @@ * `cargo run` in the compiler folder. -The resultant structs will be created in the `proto/src/prost` folder. -Build the `tendermint-proto` crate. +The resultant structs will be created in the `src/gen` folder.