|
1 | 1 | # ton-sdk-client-scala-binding |
2 | | -Scala binding for FreeTON SDK client |
3 | 2 |
|
4 | | -[](https://travis-ci.com/slavaschmidt/ton-sdk-client-scala-binding) |
| 3 | +<img src="https://i.ibb.co/rH7LV8C/Scalaton.png" width="100px" border="0"> |
| 4 | + |
| 5 | +TON SDK Client library Scala bindings. |
| 6 | + |
| 7 | +[](https://travis-ci.com/slavaschmidt/ton-sdk-client-scala-binding) |
5 | 8 | [](https://codecov.io/gh/slavaschmidt/ton-sdk-client-scala-binding) |
6 | 9 | [](http://www.apache.org/licenses/LICENSE-2.0.txt) |
7 | | -[<img src="https://img.shields.io/maven-central/v/org.scoverage/sbt-scoverage.svg?label=latest%20release"/>](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22sbt-scoverage%22) |
| 10 | +[](https://repo.maven.apache.org/maven2/com/dancingcode/freeton-sdk-client-scala-binding_2.12/1.0.0-M2/) |
| 11 | + |
| 12 | + |
| 13 | +The TON SDK Client library provides bindings for [Freeton SDK client](https://github.com/tonlabs/TON-SDK). |
| 14 | +It consists of three major parts: |
| 15 | +- JNI bindings for the rust library. Because of the way JVM interoperates with native code it is not possible to call client functions directly. |
| 16 | +Instead, a thin C wrapper is implemented. The wrapper translates Java calls into appropriate native calls predefined by the client library. |
| 17 | +- Java JNI wrapper. It is a thin layer responsible for the interoperation with the native library. Having this layer written in plain Java should allow to create bindings for other languages by reusing exising implementation. |
| 18 | +- Scala wrapper provides idiomatic type safe way to call SDK functions. |
| 19 | + |
| 20 | +## Compatibility |
| 21 | + |
| 22 | +The current version is compatible with Freeton client v1.0.0, JDK 1.8+ and Scala 2.12. |
| 23 | + |
| 24 | +We use CI on Linux Focal Fossa and MacOs X hosts to run our tests. |
| 25 | + |
| 26 | +Following systems confirmed to be compatible: |
| 27 | +- Arch Linux (manjaro 5.6.16) |
| 28 | +- Ubuntu Linux (bionic 18-04) |
| 29 | +- Windows 10 x64 |
| 30 | +- MacOs X (Catalina 10.15) |
| 31 | +- OpenJDK 8 |
| 32 | +- OpenJDK 11 |
| 33 | +- OracleJDK 1.8.0 |
| 34 | + |
| 35 | +We're currently working on adding Windows x86, Scala 2.13 and Freeton client v1.1.0 support. |
| 36 | + |
| 37 | + |
| 38 | +## Prerequisites |
| 39 | + |
| 40 | +As for any Scala application, JRE is required. This project uses SBT as a convenient build tool. Scala installation is also required. |
| 41 | +On Linux and MacOSx, all dependencies can easily be installed by [one-line installer](https://www.scala-lang.org/2020/06/29/one-click-install.html). |
| 42 | +For Windows the installation is less convenient and requires manual installation of the JRE and SBT/Scala. |
| 43 | +We recommend to use [OpenJDK](https://adoptopenjdk.net/) to install JRE. |
| 44 | +The [SBT](https://www.scala-sbt.org/download.html) can be used to install other needed components. |
| 45 | + |
| 46 | +To build native libraries the host system should possess working gcc installation for linux/windows and VisualStudio C++ for windows. |
| 47 | +It is also mandatory to have JDK (as opposed to the JRE) installed. |
| 48 | + |
| 49 | + |
| 50 | +## Installation |
| 51 | + |
| 52 | +Check the prerequisites. Clone the repository. Navigate to the project folder and start SBT by typing `sbt` in the console. |
| 53 | + |
| 54 | + |
| 55 | +## Running tests and examples |
| 56 | + |
| 57 | +The library provides a comprehensive set of tests that can be used as a reference to library usage. |
| 58 | +There is also an ongoing effort to provide a |
| 59 | +[standalone set of examples](https://github.com/slavaschmidt/freeton-sdk-client-scala-examples). |
| 60 | + |
| 61 | +Because of the way native loader works some care needs to be taken in Linux and Windows environments. |
| 62 | +We're working on improving user experience and making it as seamless as in MacOS X. |
| 63 | + |
| 64 | +The library JAR contains all needed native binaries and a custom loader. At the moment native library accessed by the JNI subsystem, |
| 65 | +the native loader will reuse existing or create appropriate native libs located in the folder defined by the java property `"java.io.freetontmpdir"`. |
| 66 | +If this property is undefined, the `lib` folder in the current project will be created (if needed) and used. |
| 67 | + |
| 68 | +The sbt scripts contain appropriate environment overrides for the default case of placing the native libraries in the "lib" subfolder |
| 69 | +but sometimes an additional user actions might be required. |
| 70 | + |
| 71 | +An additional user action is required in Linux and Windows to extend the path the OS uses to locate libraries. This is done by adding the path to the lib folder to it. |
| 72 | +For this the environment variable `LD_LIBRARY_PATH` (linux) and `PATH` (windows) must be extended with the location of the folder |
| 73 | +referenced by `"java.io.freetontmpdir"` (or if it is undefined to the `lib` folder in the project). |
| 74 | +Alternatively, the `"java.io.freetontmpdir"` can be set to point to some folder already included in system path, |
| 75 | +and the libraries well be copied by the loader as needed (given appropriate access rights). |
| 76 | + |
| 77 | +An example for Linux system: |
| 78 | +```shell script |
| 79 | +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"$(pwd/lib)" |
| 80 | +``` |
| 81 | +. |
| 82 | +In windows don't forget to restart you command line session after changing `PATH`. |
| 83 | + |
| 84 | +To run tests: navigae to the project folder and type `sbt test` |
| 85 | + |
| 86 | +To create a jar-packaged artefact: `sbt package`. The artefact with the library will be located in `target/scala-2.12` folder of the project. |
| 87 | + |
| 88 | +Building native libraries is different in linux, mac and windows, involves manual relinking on MacOs and should not be required as we provide them pre-build. |
| 89 | +Curious users can consult example build scripts for JNI headers and native libraries in `src/main/c` folder. |
| 90 | + |
| 91 | +The windows build script contains suggestions for fixing JDK header files to get them compile with VisualStudio. |
| 92 | +Small adjustments to the `ton_client.h` might also be necessary. |
| 93 | + |
| 94 | + |
| 95 | +## Working with the client |
| 96 | + |
| 97 | +Using client library involves few mandatory steps: |
| 98 | + |
| 99 | + |
| 100 | +### Load native libraries. |
| 101 | + |
| 102 | +We provide a helper class `ton.sdk.client.jni.NativeLoader` to make it as easy as possible. Simple call `NativeLoader.apply()` should suffice. Please make sure not to load the library more than once as this will lead to errors. |
| 103 | + |
| 104 | + |
| 105 | +### Instantiating client |
| 106 | + |
| 107 | +The ton client needs a configuration to be instantiated. Few common configurations that rely on default settings are [provided](https://github.com/slavaschmidt/ton-sdk-client-scala-binding/blob/c2b76dac6ac3e1a28557ea3c1f84df12b7a9074c/src/main/scala/ton/sdk/client/binding/model.scala#L39). |
| 108 | +The users of the library can easy create their own configurations by overriding the default one. Currently, the config of the ton client looks like the following: |
| 109 | +``` |
| 110 | +{ |
| 111 | + "network": { |
| 112 | + "server_address": "http://localhost", |
| 113 | + "network_retries_count": 5, |
| 114 | + "message_retries_count": 5, |
| 115 | + "message_processing_timeout": 40000, |
| 116 | + "wait_for_timeout": 40000, |
| 117 | + "out_of_sync_threshold": 15000, |
| 118 | + "access_key": "" |
| 119 | + }, |
| 120 | + "crypto": { |
| 121 | + "mnemonic_dictionary": 1, |
| 122 | + "mnemonic_word_count": 12, |
| 123 | + "hdkey_derivation_path": 'm/44"/396"/0"/0/0', |
| 124 | + "hdkey_compliant": true |
| 125 | + }, |
| 126 | + "abi": { |
| 127 | + "workchain": 0, |
| 128 | + "message_expiration_timeout": 40000, |
| 129 | + "message_expiration_timeout_grow_factor": 1.5 |
| 130 | + } |
| 131 | +} |
| 132 | +``` |
8 | 133 |
|
9 | | -ON LINUX: |
10 | 134 |
|
11 | | -export LD_LIBRARY_PATH |
| 135 | +### Creating context |
12 | 136 |
|
13 | | -ON Windows: |
| 137 | +The ton client has a concept of context. The context encapsulates configuration and state data. |
| 138 | +The context can be created by calling the `get` method of the client: |
| 139 | +```scala |
| 140 | +implicit val ctx = Context.create(ClientConfig.LOCAL).get |
| 141 | +``` |
| 142 | +and should be closed as soon as it is not needed anymore by calling |
| 143 | +```scala |
| 144 | +ctx.close() |
| 145 | +``` |
| 146 | + |
| 147 | +The binding library will try its best to auto-close forgotten contexts but because of the unpredictable nature |
| 148 | +of the JVMs garbage collection this is not always possible to do timely. |
| 149 | +Because of this the library provides managed context that is auto-closed at the moment last operation |
| 150 | +withing the context finishes execution. It can be used like in the following example: |
| 151 | +```scala |
| 152 | +import ton.sdk.client.binding.Context |
| 153 | +import Context._ |
| 154 | + |
| 155 | +val result = local { implicit ctx => |
| 156 | + call(Request.ApiReference) |
| 157 | +} |
| 158 | +``` |
| 159 | +The `local` refers to the server configuration. Client calls inside of the curly braces have the corresponding context available to them. |
| 160 | +Contexts can be nested where this makes sense by giving the implicits same name. |
| 161 | +In the case of nesting the internal context has higher order of precedence. |
| 162 | + |
| 163 | + |
| 164 | +### Calling client functions |
| 165 | + |
| 166 | +The approach to calling library functions we use called "trampolining" and in essence it means representing functions as class instances (a quite common approach in scala). |
| 167 | +This allows for type-safe calls where types of the both parameter and result are well-defined at compilation time. For example: |
| 168 | +```scala |
| 169 | +import ton.sdk.client.modules.Utils._ |
| 170 | + |
| 171 | +val result = local { implicit ctx => |
| 172 | + call(Request.ConvertAddress(accountId, AddressStringFormat.hex)) |
| 173 | +} |
| 174 | +assertValue(result)(Address(hexWorkchain)) |
| 175 | +``` |
| 176 | + |
| 177 | +Here we're calling ton client's `convert_address` function by using `ConvertAddress` case class and providing the `accountId` and required format and getting an instance of `Address` class back. |
| 178 | +The first line, `import ton.sdk.client.modules.Utils._` demonstrates how different request types reside in modules to reflect |
| 179 | +the naming schema of the ton client. |
| 180 | + |
| 181 | +For detailed description of the modules and available functions please consult [tests](src/test/scala/ton/sdk/client/modules) |
| 182 | +and documentation for the [ton client](https://github.com/tonlabs/TON-SDK/blob/master/docs). |
| 183 | + |
| 184 | + |
| 185 | +### Choosing execution style |
| 186 | + |
| 187 | +The ton client provides two modes of execution, sync and async. We decided to abstract this concept into separate "Effect". |
| 188 | +This approach makes it possible to write the application code once and then change the type of effect later without the |
| 189 | +need to touch the code itself. |
| 190 | +Of course, this approach works only for functions that support both sync and async calls. |
| 191 | + |
| 192 | +For example, following code executes asynchronously wrapped in a `Future`: |
| 193 | +```scala |
| 194 | +NativeLoader.apply() |
| 195 | + |
| 196 | +implicit val ef = futureEffect |
| 197 | + |
| 198 | +def run() = devNet { implicit ctx => |
| 199 | + for { |
| 200 | + version <- call(Client.Request.Version) |
| 201 | + seed <- call(Crypto.Request.MnemonicFromRandom()) |
| 202 | + keys <- call(Crypto.Request.MnemonicDeriveSignKeys(seed.phrase)) |
| 203 | + } yield (version, seed, keys) |
| 204 | + } |
| 205 | +``` |
| 206 | + |
| 207 | +One can made it run synchronously wrapped in a `Try` by merely changing line |
| 208 | + |
| 209 | +```scala |
| 210 | +implicit val ef = futureEffect |
| 211 | +``` |
| 212 | + |
| 213 | +to |
| 214 | + |
| 215 | +```scala |
| 216 | +implicit val ef = tryEffect |
| 217 | +``` |
| 218 | + |
| 219 | +Most of the tests written in such a way that they don't specify the type of effect and run both |
| 220 | +[synchronously](https://github.com/slavaschmidt/ton-sdk-client-scala-binding/blob/c2b76dac6ac3e1a28557ea3c1f84df12b7a9074c/src/test/scala/ton/sdk/client/modules/utilsSpec.scala#L14) |
| 221 | +and |
| 222 | +[asynchronously](https://github.com/slavaschmidt/ton-sdk-client-scala-binding/blob/c2b76dac6ac3e1a28557ea3c1f84df12b7a9074c/src/test/scala/ton/sdk/client/modules/utilsSpec.scala#L18) |
| 223 | +. |
| 224 | + |
| 225 | + |
| 226 | +#### Closing over async context |
| 227 | + |
| 228 | +There is a caveat working with asynchronous calls in managed contexts: if one closes over the context and returns async |
| 229 | +computation that runs outside of the context, the context will be closed earlier than operation can complete, for example: |
| 230 | +```scala |
| 231 | + implicit val ef = futureEffect |
| 232 | + |
| 233 | + def run() = devNet { implicit ctx => |
| 234 | + for { |
| 235 | + version <- call(Client.Request.Version) |
| 236 | + seed <- call(Crypto.Request.MnemonicFromRandom()) |
| 237 | + } yield Future(call(Crypto.Request.MnemonicDeriveSignKeys(seed.phrase))) |
| 238 | + } |
| 239 | +``` |
| 240 | + |
| 241 | +This problem is general to managed resources used in asynchronous manner and not specific to the implementation of the library. |
| 242 | + |
| 243 | + |
| 244 | +### Error handling |
| 245 | + |
| 246 | +Abstracting over the effect type gives us another advantage - it makes possible to represent failure cases in typical scala way, algebraically. |
| 247 | +The result of the `call` function returns either `Success` or `Failure` of the chosen effect type. |
| 248 | +Thus, the logic is better represented in the form of the |
| 249 | +[for-comprehension](https://github.com/slavaschmidt/ton-sdk-client-scala-binding/blob/c2b76dac6ac3e1a28557ea3c1f84df12b7a9074c/src/test/scala/ton/sdk/client/modules/processingSpec.scala#L32). |
| 250 | + |
| 251 | + |
| 252 | +### Streaming |
| 253 | + |
| 254 | +Ton client allows calling certain functions "with messages" generating continuous stream of events. |
| 255 | +We represent this in the form of "streaming" `callS` that should be called with appropriate parameters. |
| 256 | +There is a compile-time safety guarantee that the streaming function can't be called with non-streaming parameters and vice-versa. |
| 257 | +The result of streaming call is a tuple that extends "normal" result with two |
| 258 | +[blocking iterators](src/main/scala/ton/sdk/client/binding/blockingIterator.scala) |
| 259 | +to communicate arrival of messages and errors to the user. |
| 260 | +Following shows an example of using such streaming call: |
| 261 | + |
| 262 | +```scala |
| 263 | +val result = devNet { implicit ctx => |
| 264 | + for { |
| 265 | + // Prepare data for deployment message |
| 266 | + keys <- call(Crypto.Request.GenerateRandomSignKeys) |
| 267 | + signer = Signer.fromKeypair(keys) |
| 268 | + callSet = CallSet("constructor", Option(Map("pubkey" -> keys.public.asJson)), None) |
| 269 | + // Encode deployment message |
| 270 | + encoded <- call(Abi.Request.EncodeMessage(abi, None, Option(deploySet), Option(callSet), signer)) |
| 271 | + _ <- sendGrams(encoded.address) |
| 272 | + // Deploy account |
| 273 | + params = MessageEncodeParams(abi, signer, None, Option(deploySet), Option(callSet)) |
| 274 | + (data, messages, _) <- callS(Processing.Request.ProcessMessageWithEvents(params)) |
| 275 | + // Check that messages are indeed received |
| 276 | + _ = assert(messages.collect(1.minute).nonEmpty) |
| 277 | + } yield data |
| 278 | +} |
| 279 | +``` |
14 | 280 |
|
15 | | -export PATH |
| 281 | +Please note how we wait for at most one minute for all messages to arrive. |
| 282 | +Alternatively, one could wait just for, say, one second and retry until there are no messages left. |
| 283 | +Please consult the ScalaDoc of the [blocking iterators](src/main/scala/ton/sdk/client/binding/blockingIterator.scala) for further details. |
| 284 | + |
16 | 285 |
|
17 | 286 | ## License |
18 | 287 | ``` |
|
0 commit comments