Skip to content

Commit

Permalink
Dockerize TON localnet
Browse files Browse the repository at this point in the history
  • Loading branch information
swift1337 committed Sep 5, 2024
1 parent ebeedfc commit fa24973
Show file tree
Hide file tree
Showing 12 changed files with 368 additions and 0 deletions.
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:
- "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
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"]
18 changes: 18 additions & 0 deletions contrib/localnet/ton/download-jar.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

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

# Let's download 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"

# Check if the JAR file already exists
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"
12 changes: 12 additions & 0 deletions contrib/localnet/ton/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash

# Start the first process (e.g., the Java application) in the background
java -jar my-local-ton.jar with-validators-1 nogui debug &

./sidecar &

# Wait for both processes to finish
wait -n

# Optionally, handle signals or process exit
echo "One of the processes exited. Exiting container."
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,
"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

0 comments on commit fa24973

Please sign in to comment.