Skip to content

Commit e890db7

Browse files
committed
ton-sdk-toncenter module
Signed-off-by: andreypfau <[email protected]>
1 parent a748444 commit e890db7

File tree

78 files changed

+2114
-285
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+2114
-285
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mavenPublishing = "0.34.0"
1313
[libraries]
1414
kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
1515
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
16+
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
1617
datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" }
1718
atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "kotlinx-atomicfu" }
1819
serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }

provider/core/src/Provider.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package org.ton.kotlin.provider
2+
3+
public expect interface Provider {
4+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.ton.kotlin.provider
2+
3+
import kotlinx.coroutines.GlobalScope
4+
import kotlinx.coroutines.future.future
5+
import org.ton.kotlin.cell.CellRef
6+
import java.util.concurrent.CompletableFuture
7+
8+
@Suppress("OPT_IN_USAGE")
9+
public actual interface Provider {
10+
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package org.ton.kotlin.provider
2+
3+
public actual interface Provider {
4+
}

provider/liteapi/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ kotlin {
88
commonMain {
99
dependencies {
1010
api(projects.tonKotlinProviderCore)
11+
api(projects.tonKotlinLiteclient)
12+
api(libs.ktor.network)
1113
}
1214
}
1315
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.ton.kotlin.provider.liteapi
2+
3+
import org.ton.kotlin.provider.liteapi.model.LiteApiMasterchainInfo
4+
5+
public interface LiteApiClient {
6+
public suspend fun getMasterchainInfo(): LiteApiMasterchainInfo
7+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.ton.kotlin.provider.liteapi
2+
3+
import kotlinx.serialization.DeserializationStrategy
4+
import kotlinx.serialization.SerializationStrategy
5+
import kotlinx.serialization.encodeToByteArray
6+
import kotlinx.serialization.serializer
7+
import org.ton.kotlin.provider.liteapi.internal.LiteTcpConnection
8+
import org.ton.kotlin.provider.liteapi.model.LiteApiGetMasterchainInfo
9+
import org.ton.kotlin.provider.liteapi.model.LiteApiMasterchainInfo
10+
import org.ton.kotlin.provider.liteapi.model.LiteServerDesc
11+
import org.ton.kotlin.provider.liteapi.model.LiteServerQuery
12+
import org.ton.kotlin.tl.TL
13+
14+
public class LiteApiClientImpl(
15+
public val liteServerDesc: LiteServerDesc
16+
) : LiteApiClient {
17+
18+
override suspend fun getMasterchainInfo(): LiteApiMasterchainInfo =
19+
query(LiteApiGetMasterchainInfo)
20+
21+
private suspend inline fun <reified Q, reified A> query(query: Q): A = query(
22+
query,
23+
serializer<Q>(),
24+
serializer<A>(),
25+
)
26+
27+
private suspend fun <Q, A> query(
28+
query: Q,
29+
querySerializer: SerializationStrategy<Q>,
30+
answerSerializer: DeserializationStrategy<A>
31+
): A {
32+
val rawQuery = TL.Boxed.encodeToByteArray(
33+
LiteServerQuery(
34+
TL.Boxed.encodeToByteString(querySerializer, query)
35+
)
36+
)
37+
38+
val rawAnswer = LiteTcpConnection(liteServerDesc).call(rawQuery)
39+
return TL.Boxed.decodeFromByteArray(answerSerializer, rawAnswer)
40+
}
41+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package org.ton.kotlin.provider.liteapi.internal
2+
3+
import io.ktor.util.collections.*
4+
import kotlinx.coroutines.*
5+
import kotlinx.coroutines.channels.Channel
6+
import kotlinx.coroutines.sync.Mutex
7+
import kotlinx.coroutines.sync.withLock
8+
import kotlinx.io.bytestring.ByteString
9+
import kotlinx.serialization.SerializationException
10+
import kotlinx.serialization.decodeFromByteArray
11+
import org.ton.kotlin.tl.TL
12+
import kotlin.concurrent.Volatile
13+
import kotlin.properties.Delegates
14+
import kotlin.random.Random
15+
16+
internal abstract class LiteConnection {
17+
private var isTransportReady: Boolean = false
18+
private var transport: LiteTransport by Delegates.notNull()
19+
private val transportInitializationLock = Mutex()
20+
21+
private var sendJob: Job? = null
22+
private var receiveJob: Job? = null
23+
private var sendChannel: Channel<TransportMessage>? = null
24+
private val answerSubscriptions =
25+
ConcurrentMap<ByteString, suspend (LiteMessage.Answer) -> Unit>()
26+
27+
@Volatile
28+
private var clientCancelled = false
29+
30+
protected abstract suspend fun initializeTransport(): LiteTransport
31+
32+
private suspend fun initializeAndAwaitHandshakeCompletion() {
33+
if (!isTransportReady) {
34+
transportInitializationLock.withLock {
35+
if (isTransportReady) {
36+
return@withLock
37+
}
38+
39+
transport = initializeTransport()
40+
setupTransportLoops()
41+
isTransportReady = true
42+
}
43+
}
44+
}
45+
46+
private fun setupTransportLoops() {
47+
val channel = Channel<TransportMessage>(Channel.UNLIMITED)
48+
sendChannel = channel
49+
50+
sendJob = transport.launch(CoroutineName("lite-connection-send-loop")) {
51+
while (true) {
52+
val message = channel.receiveCatching().getOrNull() ?: break
53+
transport.send(message)
54+
}
55+
}
56+
57+
receiveJob = transport.launch(CoroutineName("lite-connection-receive-loop")) {
58+
while (true) {
59+
val incoming = transport.receiveCatching().getOrNull() ?: break
60+
processTransportMessage(incoming)
61+
}
62+
}
63+
64+
transport.coroutineContext.job.invokeOnCompletion {
65+
sendJob?.cancel()
66+
receiveJob?.cancel()
67+
clientCancelled = true
68+
answerSubscriptions.clear()
69+
channel.close(it)
70+
sendChannel = null
71+
}
72+
}
73+
74+
suspend fun call(
75+
call: ByteArray,
76+
): ByteArray {
77+
if (clientCancelled) {
78+
error("LiteConnection was canceled")
79+
}
80+
81+
initializeAndAwaitHandshakeCompletion()
82+
83+
val queryId = ByteString(*Random.nextBytes(32))
84+
val channel = Channel<LiteMessage.Answer>()
85+
try {
86+
val query = LiteMessage.Query(queryId, ByteString(*call))
87+
88+
subscribeToAnswer(queryId) { message ->
89+
channel.send(message)
90+
}
91+
92+
sendMessage(query)
93+
94+
return channel.receiveCatching().getOrThrow().query.toByteArray()
95+
} catch (e: CancellationException) {
96+
throw e
97+
} finally {
98+
channel.close()
99+
unsubscribeFromAnswers(queryId)
100+
}
101+
}
102+
103+
private fun subscribeToAnswer(
104+
queryId: ByteString,
105+
subscription: suspend (LiteMessage.Answer) -> Unit,
106+
) {
107+
answerSubscriptions.computeIfAbsent(queryId) {
108+
subscription
109+
}
110+
}
111+
112+
private fun unsubscribeFromAnswers(queryId: ByteString) {
113+
answerSubscriptions.remove(queryId)
114+
}
115+
116+
private suspend fun sendMessage(message: LiteMessage) {
117+
val data = TL.Boxed.encodeToByteArray(LiteMessage.serializer(), message)
118+
sendTransportMessage(TransportMessage(data))
119+
}
120+
121+
private suspend fun sendTransportMessage(transportMessage: TransportMessage) {
122+
val channel = checkNotNull(sendChannel) {
123+
"Transport channel is not initialized"
124+
}
125+
channel.send(transportMessage)
126+
}
127+
128+
private suspend fun processTransportMessage(transportMessage: TransportMessage) {
129+
val message = decodeMessage(transportMessage) ?: return
130+
processMessage(message)
131+
}
132+
133+
private fun decodeMessage(transportMessage: TransportMessage): LiteMessage? {
134+
try {
135+
return TL.Boxed.decodeFromByteArray<LiteMessage>(transportMessage.value)
136+
} catch (_: SerializationException) {
137+
} catch (_: IllegalArgumentException) {
138+
}
139+
return null
140+
}
141+
142+
private suspend fun processMessage(message: LiteMessage) {
143+
when (message) {
144+
is LiteMessage.Query -> processQuery(message)
145+
is LiteMessage.Answer -> processAnswer(message)
146+
is LiteMessage.Authentificate -> processAuthentificate(message)
147+
is LiteMessage.AuthentificationComplete -> processAuthentificationComplete(message)
148+
is LiteMessage.AuthentificationNonce -> processAuthentificationNonce(message)
149+
is LiteMessage.Ping -> processPing(message)
150+
is LiteMessage.Pong -> processPong(message)
151+
}
152+
}
153+
154+
private fun processQuery(message: LiteMessage.Query) {}
155+
156+
private suspend fun processAnswer(message: LiteMessage.Answer) {
157+
answerSubscriptions[message.queryId]?.invoke(message)
158+
}
159+
160+
private fun processAuthentificate(message: LiteMessage.Authentificate) {}
161+
162+
private fun processAuthentificationComplete(message: LiteMessage.AuthentificationComplete) {}
163+
164+
private fun processAuthentificationNonce(message: LiteMessage.AuthentificationNonce) {}
165+
166+
private fun processPing(message: LiteMessage.Ping) {}
167+
168+
private fun processPong(message: LiteMessage.Pong) {}
169+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
@file:UseSerializers(ByteStringBase64Serializer::class)
2+
3+
package org.ton.kotlin.provider.liteapi.internal
4+
5+
import kotlinx.io.bytestring.ByteString
6+
import kotlinx.serialization.SerialName
7+
import kotlinx.serialization.Serializable
8+
import kotlinx.serialization.UseSerializers
9+
import org.ton.kotlin.crypto.PublicKey
10+
import org.ton.kotlin.tl.Bits256
11+
import org.ton.kotlin.tl.TlConstructorId
12+
import org.ton.kotlin.tl.serializers.ByteStringBase64Serializer
13+
14+
@Serializable
15+
internal sealed interface LiteMessage {
16+
@Serializable
17+
@SerialName("adnl.message.query")
18+
@TlConstructorId(0xb48bf97a)
19+
class Query(
20+
@Bits256
21+
val queryId: ByteString,
22+
val query: ByteString
23+
) : LiteMessage
24+
25+
@Serializable
26+
@SerialName("adnl.message.answer")
27+
@TlConstructorId(0x0fac8416)
28+
class Answer(
29+
@Bits256
30+
val queryId: ByteString,
31+
val query: ByteString
32+
) : LiteMessage
33+
34+
@Serializable
35+
@SerialName("tcp.ping")
36+
@TlConstructorId(0x4d082b9a)
37+
class Ping(
38+
val randomId: Long
39+
) : LiteMessage
40+
41+
@Serializable
42+
@SerialName("tcp.pong")
43+
@TlConstructorId(0xdc69fb03)
44+
class Pong(
45+
val randomId: Long
46+
) : LiteMessage
47+
48+
@Serializable
49+
@SerialName("tcp.authentificate")
50+
@TlConstructorId(0x445bab12)
51+
class Authentificate(val nonce: ByteString) : LiteMessage
52+
53+
@Serializable
54+
@SerialName("tcp.authentificationNonce")
55+
@TlConstructorId(0xe35d4ab6)
56+
class AuthentificationNonce(val nonce: ByteString) : LiteMessage
57+
58+
@Serializable
59+
@SerialName("tcp.authentificationComplete")
60+
@TlConstructorId(0xf7ad9ea6)
61+
class AuthentificationComplete(val key: PublicKey, val signature: ByteString) : LiteMessage
62+
}

0 commit comments

Comments
 (0)