Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ton): TON localnet integration #2833

Merged
merged 21 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ solana:
@echo "Building solana docker image"
$(DOCKER) build -t solana-local -f contrib/localnet/solana/Dockerfile contrib/localnet/solana/

ton:
@echo "Building ton docker image"
contrib/localnet/ton/download-jar.sh
$(DOCKER) buildx build --platform linux/amd64 -t ton-local -f contrib/localnet/ton/Dockerfile contrib/localnet/ton/

start-e2e-test: zetanode
@echo "--> Starting e2e test"
cd contrib/localnet/ && $(DOCKER_COMPOSE) up -d
Expand Down
14 changes: 14 additions & 0 deletions contrib/localnet/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,20 @@ services:
ipv4_address: 172.20.0.103
entrypoint: [ "/usr/bin/start-solana.sh" ]

ton:
image: ton-local:latest
container_name: ton
hostname: ton
profiles:
- ton
- all
ports:
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
- "8111:8000" # sidecar
- "4443:4443" # lite client
networks:
mynetwork:
ipv4_address: 172.20.0.104

orchestrator:
image: orchestrator:latest
tty: true
Expand Down
1 change: 1 addition & 0 deletions contrib/localnet/ton/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.jar
34 changes: 34 additions & 0 deletions contrib/localnet/ton/Dockerfile
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Let's build a sidecar
FROM golang:1.20 AS go-builder
WORKDIR /opt/sidecar
COPY ./sidecar.go .
RUN go build -o /opt/sidecar/sidecar sidecar.go

FROM openjdk:24-slim AS ton-node

ARG WORKDIR="/opt/my-local-ton"

# Install dependencies && drop apt cache
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl jq vim lsb-release \
&& rm -rf /var/lib/apt/lists/*

COPY ./my-local-ton.jar $WORKDIR/
COPY ./entrypoint.sh $WORKDIR/

COPY --from=go-builder /opt/sidecar $WORKDIR/

WORKDIR $WORKDIR

# Ensure whether the build works or not.
RUN chmod +x entrypoint.sh && chmod +x sidecar \
&& java -jar my-local-ton.jar debug nogui test-binaries;

# Lite Client
EXPOSE 4443

# Sidecar
EXPOSE 8000

ENTRYPOINT ["/opt/my-local-ton/entrypoint.sh"]
17 changes: 17 additions & 0 deletions contrib/localnet/ton/download-jar.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

script_dir=$(cd -- "$(dirname -- "$0")" &> /dev/null && pwd)

# Downloads JAR outside of the Dockerfile to avoid re-downloading it every time during rebuilds.
jar_version="v120"
jar_url="https://github.com/neodix42/MyLocalTon/releases/download/${jar_version}/MyLocalTon-x86-64.jar"
jar_file="$script_dir/my-local-ton.jar"

if [ -f "$jar_file" ]; then
echo "File $jar_file already exists. Skipping download."
exit 0
fi

echo "File not found. Downloading..."
echo "URL: $jar_url"
wget -q --show-progress -O "$jar_file" "$jar_url"
8 changes: 8 additions & 0 deletions contrib/localnet/ton/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

java -jar my-local-ton.jar with-validators-1 nogui debug &

./sidecar &

# Wait for both processes to finish
wait -n
61 changes: 61 additions & 0 deletions contrib/localnet/ton/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# TON localnet

This docker image represents a fully working TON node with 1 validator and a faucet wallet.

## How it works

- It uses [my-local-ton](https://github.com/neodix42/MyLocalTon) project without GUI.
Port `4443` is used for [lite-client connection](https://docs.ton.org/participate/run-nodes/enable-liteserver-node).
- It also has a convenient sidecar on port `8000` with some useful tools.
- Please note that it might take **several minutes** to bootstrap the network.

## Sidecar

### Getting faucet wallet

```shell
curl -s http://ton:8000/faucet.json | jq
{
"initialBalance": 1000001000000000,
swift1337 marked this conversation as resolved.
Show resolved Hide resolved
"privateKey": "...",
"publicKey": "...",
"walletRawAddress": "...",
"mnemonic": "...",
"walletVersion": "V3R2",
"workChain": 0,
"subWalletId": 42,
"created": false
}
```

### Getting lite client config

Please note that the config returns IP of localhost (`int 2130706433`).
You need to replace it with the actual IP of the docker container.

```shell
curl -s http://ton:8000/lite-client.json | jq
{
"@type": "config.global",
"dht": { ... },
"liteservers": [
{
"id": { "key": "...", "@type": "pub.ed25519" },
"port": 4443,
"ip": 2130706433
}
],
"validator": { ... }
}
```
### Checking the node status
It checks for config existence and the fact of faucet wallet deployment
```shell
curl -s http://ton:8000/status | jq
{
"status": "OK"
}
```
124 changes: 124 additions & 0 deletions contrib/localnet/ton/sidecar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
)

const (
port = ":8000"

basePath = "/opt/my-local-ton/myLocalTon"
liteClientConfigPath = basePath + "/genesis/db/my-ton-local.config.json"
settingsPath = basePath + "/settings.json"

faucetJSONKey = "faucetWalletSettings"
)

func main() {
http.HandleFunc("/faucet.json", errorWrapper(faucetHandler))
http.HandleFunc("/lite-client.json", errorWrapper(liteClientHandler))
http.HandleFunc("/status", errorWrapper(statusHandler))

//nolint:gosec
if err := http.ListenAndServe(port, nil); err != nil {
log.Fatal(err)
}
}

func errorWrapper(handler func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := handler(w, r); err != nil {
errResponse(w, http.StatusInternalServerError, err)
}
}
}

// Handler for the /faucet.json route
func faucetHandler(w http.ResponseWriter, _ *http.Request) error {
faucet, err := extractFaucetFromSettings(settingsPath)
if err != nil {
return err
}

jsonResponse(w, http.StatusOK, faucet)
return nil
}

func liteClientHandler(w http.ResponseWriter, _ *http.Request) error {
data, err := os.ReadFile(liteClientConfigPath)
if err != nil {
return fmt.Errorf("could not read lite client config: %w", err)
}

jsonResponse(w, http.StatusOK, json.RawMessage(data))
return nil
}

// Handler for the /status route
func statusHandler(w http.ResponseWriter, _ *http.Request) error {
if _, err := os.Stat(liteClientConfigPath); err != nil {
return fmt.Errorf("lite client config %q not found: %w", liteClientConfigPath, err)
}

faucet, err := extractFaucetFromSettings(settingsPath)
if err != nil {
return err
}

type faucetShape struct {
Created bool `json:"created"`
}

var fs faucetShape
if err = json.Unmarshal(faucet, &fs); err != nil {
return fmt.Errorf("failed to parse faucet settings: %w", err)
}

if !fs.Created {
return errors.New("faucet is not created yet")
}

jsonResponse(w, http.StatusOK, map[string]string{"status": "OK"})
return nil
}

func extractFaucetFromSettings(filePath string) (json.RawMessage, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("could not read faucet settings: %w", err)
}

var keyValue map[string]json.RawMessage
if err := json.Unmarshal(data, &keyValue); err != nil {
return nil, fmt.Errorf("failed to parse faucet settings: %w", err)
}

faucet, ok := keyValue[faucetJSONKey]
if !ok {
return nil, errors.New("faucet settings not found in JSON")
}

return faucet, nil
}

func errResponse(w http.ResponseWriter, status int, err error) {
jsonResponse(w, status, map[string]string{"error": err.Error()})
}

func jsonResponse(w http.ResponseWriter, status int, data any) {
bytes, err := json.Marshal(data)
if err != nil {
bytes = []byte("Failed to marshal JSON")
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)

//nolint:errcheck
w.Write(bytes)
}
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,12 @@ require (
sigs.k8s.io/yaml v1.4.0 // indirect
)

require (
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect
github.com/snksoft/crc v1.1.0 // indirect
github.com/tonkeeper/tongo v1.9.3 // indirect
)

replace (
github.com/agl/ed25519 => github.com/binance-chain/edwards25519 v0.0.0-20200305024217-f36fc4b53d43
github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.22.3
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,8 @@ github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY=
github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
Expand Down Expand Up @@ -1445,6 +1447,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4=
github.com/snksoft/crc v1.1.0 h1:HkLdI4taFlgGGG1KvsWMpz78PkOC9TkPVpTV/cuWn48=
github.com/snksoft/crc v1.1.0/go.mod h1:5/gUOsgAm7OmIhb6WJzw7w5g2zfJi4FrHYgGPdshE+A=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
Expand Down Expand Up @@ -1563,6 +1567,8 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tonkeeper/tongo v1.9.3 h1:VNIZIuPeMw0+KZPvP57+EbgRwGZocN2v5CulRxba20A=
github.com/tonkeeper/tongo v1.9.3/go.mod h1:MjgIgAytFarjCoVjMLjYEtpZNN1f2G/pnZhKjr28cWs=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
Expand Down
63 changes: 63 additions & 0 deletions zetaclient/chains/ton/observer/observer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package observer

import (
"context"
"encoding/json"
"strings"
"testing"

"github.com/stretchr/testify/require"
"github.com/tonkeeper/tongo/config"
"github.com/tonkeeper/tongo/liteapi"
)

// todo tmp (will be resolved automatically)
// taken from ton:8000/lite-client.json
const configRaw = `{"@type":"config.global","dht":{"@type":"dht.config.global","k":3,"a":3,"static_nodes":
{"@type":"dht.nodes","nodes":[]}},"liteservers":[{"id":{"key":"+DjLFqH/N5jO1ZO8PYVYU6a6e7EnnsF0GWFsteE+qy8=","@type":
"pub.ed25519"},"port":4443,"ip":2130706433}],"validator":{"@type":"validator.config.global","zero_state":
{"workchain":-1,"shard":-9223372036854775808,"seqno":0,"root_hash":"rR8EFZNlyj3rfYlMyQC8gT0A6ghDrbKe4aMmodiNw6I=",
"file_hash":"fT2hXGv1OF7XDhraoAELrYz6wX3ue16QpSoWTiPrUAE="},"init_block":{"workchain":-1,"shard":-9223372036854775808,
"seqno":0,"root_hash":"rR8EFZNlyj3rfYlMyQC8gT0A6ghDrbKe4aMmodiNw6I=",
"file_hash":"fT2hXGv1OF7XDhraoAELrYz6wX3ue16QpSoWTiPrUAE="}}}`

func TestObserver(t *testing.T) {
t.Skip("skip test")

ctx := context.Background()

cfg, err := config.ParseConfig(strings.NewReader(configRaw))
require.NoError(t, err)

client, err := liteapi.NewClient(liteapi.WithConfigurationFile(*cfg))
require.NoError(t, err)

res, err := client.GetMasterchainInfo(ctx)
require.NoError(t, err)

// Outputs:
// {
// "Last": {
// "Workchain": 4294967295,
// "Shard": 9223372036854775808,
// "Seqno": 915,
// "RootHash": "2e9e312c5bd3b7b96d23ce1342ac76e5486012c9aac44781c2c25dbc55f5c8ad",
// "FileHash": "d3745319bfaeebb168d9db6bb5b4752b6b28ab9041735c81d4a02fc820040851"
// },
// "StateRootHash": "02538fb9dc802004012285a90a7af9ba279706e2deea9ca635decd80e94a7045",
// "Init": {
// "Workchain": 4294967295,
// "RootHash": "ad1f04159365ca3deb7d894cc900bc813d00ea0843adb29ee1a326a1d88dc3a2",
// "FileHash": "7d3da15c6bf5385ed70e1adaa0010bad8cfac17dee7b5e90a52a164e23eb5001"
// }
// }
t.Logf("Masterchain info")
logJSON(t, res)
}

func logJSON(t *testing.T, v any) {
b, err := json.MarshalIndent(v, "", " ")
require.NoError(t, err)

t.Log(string(b))
}
Loading
Loading