Skip to content

Project structure

eloylp edited this page May 29, 2023 · 5 revisions

After some iterations, we reached what seems a good, maintainable project structure. On this document we will analyse how the code is being structured and why. The following tree illustrates the relevant parts of the current project structure:

.
├── CONTRIBUTING.md
├── docker
│   └── Dockerfile
├── lib
│   ├── uniffi-bindgen
│   ├── uniffi-zcash
│   ├── uniffi-zcash-cli
│   └── uniffi-zcash-test
├── LICENSE
└── README.md

Lets describe a bit the above tree:

  • lib/uniffi-bindgen - This crate hosts the Mozilla UniFFI bindgen CLI. They don't provide anymore a pre-built binary, so this crate is invoked directly when needed as a binary application.
  • lib/uniffi-zcash - Here is where all our FFI code resides. Most of the work is taking place here. We will analyse this in depth later.
  • lib/uniffi-zcash-cli - The internal repo workflows management tool.
  • lib/uniffi-zcash-test - Testing related tools.

All the FFI exposed code is under the same crate, lib/uniffi-zcash. This is due to a limitation; The external types feature that would allow us to import external stuff, is not currently available in all target languages, as we documented in the milestone 3 notes. Lets see the approximate structure of the lib/uniffi-zcash folder:

.
├── build.rs
├── Cargo.toml
├── src
│   ├── bin
│   ├── error.rs
│   ├── hdwallet
│   ├── lib.rs
│   ├── orchard
│   ├── payment.rs
│   ├── secp256k1
│   ├── udl
│   ├── utils.rs
│   ├── zcash_client_backend
│   ├── zcash_primitives
│   └── zcash.udl
├── tests
│   ├── kotlin
│   ├── python
│   ├── ruby
│   ├── swift
│   └── test_generated_bindings.rs
└── uniffi.toml

Now, lets analyse the relevant elements of the above tree. We can clearly see how we are trying to mimic the original Zcash api package structure in our Rust wrapper code. Elements like src/zcash_primitives, src/orchard or src/hdwallet are revealing this intention.

There are also other elements like the src/error.rs file, which contains an enum which acts as an umbrella interface for all the errors returned by the original Zcash API. New exposed errors should be properly mapped there.

All the tests for the different target languages are under the test/* respective folders. Being the file tests/test_generated_bindings.rs where all tests should be declared for later execution by the Mozilla UniFFI library.

The Mozilla UniFFI tool only expects one namespace/udl file per build. This was complicated to manage as the UDL file started getting big. Our solution to this, was to use the build.rs file for concatenating all the different Mozilla UniFFI UDL files (which are under the src/udl folder) before compilation takes place. If we look inside the src/udl folder, we can observe how, similar to the rust wrapping code, we are trying to mimic the original Zcash API package structure:

.
├── error.udl
├── hdwallet
│   └── extended_key
├── namespace.udl
├── orchard
│   ├── address.udl
│   ├── diversifier_index.udl
│   └── keys
├── payment.udl
├── secp256k1
│   └── secret_key.udl
├── zcash_client_backend
│   ├── address
│   └── keys
└── zcash_primitives
    ├── common.udl
    ├── consensus
    ├── legacy
    ├── memo
    ├── sapling
    ├── transaction
    └── zip32

But all this mimicking in Rust wrapped code and UDL files are only for the developer perspective, as the Mozilla UniFFI project expects everything to be in the same package. That is the reason because we can observe the following code in the lib.rs and sub modules mod.rs files:

mod error;
pub use error::*;

mod payment;
pub use self::payment::*;

mod hdwallet;
pub use self::hdwallet::*;

mod secp256k1;
pub use self::secp256k1::*;

The above code means we need to bring all the types to the root namespace, or the Mozilla UNIFFi would not be able to find the types. Its not perfect, as it forces us to have verbose type names like ZcashOrchardIncomingViewingKey, but this currently provides separation of the different parts and avoids conflicts when multiple developers are working in the project. For API users, currently this means a flat structure in which every type is placed at the same level. This could change whenever we find a proper alternative.

Clone this wiki locally