Skip to content

Commit 2801046

Browse files
authored
M2 Cleanup (#10)
* Complete windows build * Renamed windows DLL * Improved Readme
1 parent c2b76da commit 2801046

File tree

5 files changed

+286
-16
lines changed

5 files changed

+286
-16
lines changed

README.md

Lines changed: 276 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,287 @@
11
# ton-sdk-client-scala-binding
2-
Scala binding for FreeTON SDK client
32

4-
[![Build Status](https://travis-ci.com/slavaschmidt/ton-sdk-client-scala-binding.svg?branch=main)](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+
[![Build Status](https://travis-ci.com/slavaschmidt/ton-sdk-client-scala-binding.svg?branch=main&env=BADGE=osx)](https://travis-ci.com/slavaschmidt/ton-sdk-client-scala-binding)
58
[![codecov](https://codecov.io/gh/slavaschmidt/ton-sdk-client-scala-binding/branch/main/graph/badge.svg?token=MRUA0KJ2BK)](https://codecov.io/gh/slavaschmidt/ton-sdk-client-scala-binding)
69
[![License](http://img.shields.io/:license-Apache%202-red.svg)](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+
[![Maven Central](https://img.shields.io/maven-central/v/com.dancingcode/freeton-sdk-client-scala-binding_2.12)](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+
```
8133

9-
ON LINUX:
10134

11-
export LD_LIBRARY_PATH
135+
### Creating context
12136

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+
```
14280

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+
16285

17286
## License
18287
```

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name := "freeton-sdk-client-scala-binding"
22

3-
version := "1.0.0-M2"
3+
version := "1.0.0-M3"
44

55
organization := "com.dancingcode"
66

File renamed without changes.

src/main/scala/ton/sdk/client/binding/Context.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ object Context {
336336
override def map[P, R](in: Future[P])(f: P => R): Future[R] = in.map(f)
337337
override def recover[R, U >: R](in: Future[R])(pf: PartialFunction[Throwable, U]): Future[U] = in.recover(pf)
338338

339-
override def unsafeGet[R](a: Future[R]): R = Await.result(a, 60.seconds)
339+
override def unsafeGet[R](a: Future[R]): R = Await.result(a, 180.seconds)
340340
}
341341

342342
// the context creation is within Try so we need to "Futurize" the result

src/main/scala/ton/sdk/client/jni/NativeLoader.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class NativeLoader {
2020
private static final String jniLibName = "TonSdkClientJniBinding";
2121
private static final String tonClientLibName = "ton_client";
2222
private static final String javaProp = "java.library.path";
23+
private static final String JAVA_IO_FREETONTMPDIR = "java.io.freetontmpdir";
2324

2425

2526
public static void apply() throws Exception {
@@ -48,13 +49,13 @@ private static void addPath(String path) {
4849
}
4950
}
5051

51-
private static String libsDir() {
52-
String outer = System.getProperty("java.io.freetontmpdir");
53-
if (outer == null) {
54-
return new File("lib").getAbsolutePath();
55-
} else {
56-
return outer;
57-
}
52+
private static String libsDir() {
53+
String outer = System.getProperty(JAVA_IO_FREETONTMPDIR);
54+
if (outer == null) {
55+
return new File("lib").getAbsolutePath();
56+
} else {
57+
return outer;
58+
}
5859
}
5960

6061
private static boolean libsAreThere(String path) {

0 commit comments

Comments
 (0)