1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
//! This crate allows you to distributed heavy-workload functions to the Golem Network (or any
//! other compatible backend when support for it is added in the future).
//!
//! ## Quick start
//!
//! The usage is pretty straightforward. In your `Cargo.toml`, put `gfaas` as your dependency
//!
//! ```toml
//! # Cargo.toml
//! [dependencies]
//! gfaas = "0.3"
//! ```
//!
//! You can now annotate some heavy-workload function to be distributed on the Golem Network
//! like so
//!
//! ```rust,ignore
//! use gfaas::remote_fn;
//!
//! #[remote_fn]
//! fn hello(input: String) -> String {
//!     // let's make the input all-caps
//!     input.to_uppercase().to_string()
//! }
//!
//! #[actix_rt::main]
//! async fn main() {
//!     let input = "hey there gfaas";
//!     let output = hello("hey there gfaas".to_string()).await.unwrap();
//!     assert_eq!(input.to_uppercase(), output)
//! }
//! ```
//!
//! In order to compile your code you'll need to use our custom wrapper on top of `cargo` called
//! `gfaas`. You can install the tool with `cargo-install` like so
//!
//! ```sh
//! cargo install gfaas-cli
//! ```
//!
//! Then, you can use `gfaas` like you would use `cargo`. So, to build and run on Golem Network,
//! you'd execute
//!
//! ```sh
//! gfaas run
//! ```
//!
//! ## Notes about `gfaas::remote_fn`
//!
//! When you annotate a function with `gfaas::remote_fn` attribute, it gets expanded into a
//! full-fledged async function which is fallible. So for instance, the following function
//!
//! ```rust,ignore
//! use gfaas::remote_fn;
//!
//! #[remote_fn]
//! fn hello(input: String) -> String;
//! ```
//!
//! expands into
//!
//! ```rust,ignore
//! async fn hello(input: String) -> Result<String, gfaas::Error>;
//! ```
//!
//! Therefore, it is important to remember that you need to run the function in an async block
//! and in order to get the result of your function back, you need to unpack it from the outer
//! `Result` type.
//!
//! The asyncness and the `Result` are there due to the nature of any distributed computation
//! run on top of some network of nodes: it may fail due to reasons not related to your app
//! such as network downtime, etc.
//!
//! Furthermore, the input and output arguments of your function have to be serializable, and
//! so they are expected to derive `serde::Serialize` and `serde::Deserialize` traits.
//!
//! ### Specifying Golem's configuration parameters
//! 
//! You can currently set the following configuration parameters directly via `gfaas::remote_fn`
//! attribute:
//! 
//! * (maximum) budget in NGNT (defaults to 100):
//! 
//! ```rust,ignore
//! #[remote_fn(budget = 100)]
//! fn hello(input: String) -> String;
//! ```
//! 
//! * timeout in seconds (defaults to 10 minutes):
//! 
//! ```rust,ignore
//! #[remote_fn(timeout = 600)]
//! fn hello(input: String) -> String;
//! ```
//! 
//! * subnet tag (defaults to "devnet-alpha.2"):
//! 
//! ```rust,ignore
//! #[remote_fn(subnet = "devnet-alpha.2")]
//! fn hello(input: String) -> String;
//! ```
//! 
//! Of course, nobody stops you from setting any number of parameters at once
//! 
//! ```rust,ignore
//! #[remote_fn(budget = 10, subnet = "my_subnet")]
//! fn hello(input: String) -> String;
//! ```
//!
//! ## Notes about `gfaas` build tool and adding dependecies for your functions
//!
//! The reason that a custom wrapper around `cargo` is needed, is because the function
//! annotated with `gfaas::remote_fn`, under-the-hood is actually automatically cross-compiled
//! into a WASI binary.
//!
//! In addition, since the functions are cross-compiled to WASI, you need to install
//! `wasm32-wasi` target in your used Rust toolchain. Furthermore, for that same reason, not
//! all crates are compatible with WASI yet, but you can manually specify which crates you
//! want your functions to depend on by adding a `[gfaas_dependencies]` section to your `Cargo.toml`
//!
//! ```toml
//! # Cargo.toml
//! [package]
//! author = "Jakub Konka"
//!
//! [dependecies]
//! actix = "1"
//!
//! [gfaas_dependencies]
//! log = "0.4"
//! ```
//!
//! ## Notes on running your app locally (for testing)
//! 
//! It is well known that prior to launching our app on some distributed network of nodes, it
//! is convenient to first test the app locally in search of bugs and errors. This is also
//! possible with `gfaas`. In order to force your app to run locally, simply pass
//! `run_local=true` as argument to `gfaas::remote_fn` attribute
//! 
//! ```rust,ignore
//! #[remote_fn(run_local = true)]
//! fn hello(input: String) -> String;
//! ```
//! 
//! This will spawn all of your annotated functions in separate threads on your machine locally,
//! so you can verify that everything works as expected prior to launching the tasks on the
//! Golem Network.
//!
//! ## Examples
//!
//! A couple illustrative examples of how to use this crate can be found in the `examples/`
//! directory. All examples require `gfaas` build tool to be built.

pub mod __private {
    //! This is a private module. The stability of this API is not guaranteed and may change
    //! without notice in the future.
    pub use anyhow;
    pub use futures;
    pub use serde_json;
    pub use tempfile;
    pub use tokio;
    pub use ya_agreement_utils;
    pub use ya_runtime_wasi;
    pub use yarapi;

    #[allow(unused)]
    pub mod package {
        //! This private module describes the structures concerning Yagna packages.
        use anyhow::Result;
        use std::{
            fs,
            io::{Cursor, Write},
            path::Path,
        };
        use zip::{write::FileOptions, CompressionMethod, ZipWriter};

        /// Represents Yagna package which internally is represented as a zip archive.
        pub struct Package {
            zip_writer: ZipWriter<Cursor<Vec<u8>>>,
            options: FileOptions,
            module_name: Option<String>,
        }

        impl Package {
            /// Creates new empty Yagna package.
            pub fn new() -> Self {
                let options = FileOptions::default().compression_method(CompressionMethod::Stored);
                let zip_writer = ZipWriter::new(Cursor::new(Vec::new()));

                Self {
                    zip_writer,
                    options,
                    module_name: None,
                }
            }

            /// Adds a Wasm modules from path.
            pub fn add_module_from_path<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
                let module_name = path
                    .as_ref()
                    .file_name()
                    .unwrap()
                    .to_str()
                    .unwrap()
                    .to_owned();
                let contents = fs::read(path.as_ref())?;
                self.zip_writer
                    .start_file(&module_name, self.options.clone())?;
                self.zip_writer.write(&contents)?;
                self.module_name = Some(module_name);

                Ok(())
            }

            /// Write the package to file at the given path.
            pub fn write<P: AsRef<Path>>(mut self, path: P) -> Result<()> {
                // create manifest
                let comps: Vec<_> = self.module_name.as_ref().unwrap().split('.').collect();
                let manifest = serde_json::json!({
                    "id": "custom",
                    "name": "custom",
                    "entry-points": [{
                        "id": comps[0],
                        "wasm-path": self.module_name.unwrap(),
                    }],
                    "mount-points": [{
                        "rw": "workdir",
                    }]
                });
                self.zip_writer
                    .start_file("manifest.json", self.options.clone())?;
                self.zip_writer.write(&serde_json::to_vec(&manifest)?)?;

                let finalized = self.zip_writer.finish()?.into_inner();
                fs::write(path.as_ref(), finalized)?;

                Ok(())
            }
        }
    }
}

/// The bread and butter of this crate.
///
/// ## Specifying custom Golem datadir and budget
///
/// It is possible, via the attribute, to specify a custom Golem datadir location and budget
///
/// ```rust,ignore
/// use gfaas::remote_fn;
///
/// #[remote_fn(
///     datadir = "/Users/kubkon/golem/datadir",
///     budget = 100,
/// )]
/// fn hello(input: String) -> String;
/// ```
pub use gfaas_macro::remote_fn;

/// Re-export of `anyhow::Error` which is the default type returned by the expanded
/// `gfaas::remote_fn`-annotated function.
pub use anyhow::Error;