Skip to content

Commit 4ab9729

Browse files
authored
TON: fix http server port mapping (#2120)
* fix: TON http server port mapping * chore: resolve conflict * chore: changeset
1 parent 9bdb5d0 commit 4ab9729

File tree

4 files changed

+72
-109
lines changed

4 files changed

+72
-109
lines changed

book/src/framework/components/blockchains/ton.md

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,27 @@ More info on parameters can be found here <https://github.com/neodix42/mylocalto
3030

3131
## Network Configuration
3232

33-
The framework provides seamless access to the TON network configuration by embedding the config URL directly in the node URLs. The `ExternalHTTPUrl` and `InternalHTTPUrl` include the full path to `localhost.global.config.json`, which can be used directly with `liteclient.GetConfigFromUrl()` without additional URL formatting.
33+
The framework provides direct access to TON liteserver connections through pre-formatted liteserver URLs. The `ExternalHTTPUrl` and `InternalHTTPUrl` contain ready-to-use liteserver connection strings in the format `liteserver://publickey@host:port`, eliminating the need to fetch and parse separate configuration files.
3434

3535
## Default Ports
3636

37-
The TON implementation exposes essential services:
37+
The TON implementation exposes essential services through port mapping:
3838

39-
* TON Simple HTTP Server: Port 8000
40-
* TON Lite Server: Port derived from base port + 100
39+
* **TON Simple HTTP Server**: External dynamic port → Internal port 8000
40+
* **TON Lite Server**: External dynamic port → Internal dynamic port (base + 100 offset)
4141

42-
> Note: `tonutils-go` library is used for TON blockchain interactions, which requires a TON Lite Server connection. The framework embeds the config URL directly in the node URLs for convenient access to the global configuration file needed by `tonutils-go`.
42+
The framework automatically handles port mapping and provides ready-to-use liteserver connection URLs.
43+
44+
> Note: `tonutils-go` library is used for TON blockchain interactions, which requires a TON Lite Server connection. The framework provides direct liteserver:// URLs that can be used immediately without additional configuration parsing.
4345
4446
## Usage
4547

4648
```go
4749
package examples
4850

4951
import (
52+
"context"
53+
"fmt"
5054
"strings"
5155
"testing"
5256

@@ -63,6 +67,34 @@ type CfgTon struct {
6367
BlockchainA *blockchain.Input `toml:"blockchain_a" validate:"required"`
6468
}
6569

70+
// getConnectionPoolFromLiteserverURL parses a liteserver:// URL and creates a connection pool
71+
func getConnectionPoolFromLiteserverURL(ctx context.Context, liteserverURL string) (*liteclient.ConnectionPool, error) {
72+
// Parse the liteserver URL, expected format: liteserver://publickey@host:port
73+
if !strings.HasPrefix(liteserverURL, "liteserver://") {
74+
return nil, fmt.Errorf("invalid liteserver URL format: expected liteserver:// prefix")
75+
}
76+
77+
// Remove the liteserver:// prefix
78+
urlPart := strings.TrimPrefix(liteserverURL, "liteserver://")
79+
80+
// Split by @ to separate publickey and host:port
81+
parts := strings.Split(urlPart, "@")
82+
if len(parts) != 2 {
83+
return nil, fmt.Errorf("invalid liteserver URL format: expected publickey@host:port")
84+
}
85+
86+
publicKey := parts[0]
87+
hostPort := parts[1]
88+
89+
connectionPool := liteclient.NewConnectionPool()
90+
err := connectionPool.AddConnection(ctx, hostPort, publicKey)
91+
if err != nil {
92+
return nil, fmt.Errorf("failed to add liteserver connection: %w", err)
93+
}
94+
95+
return connectionPool, nil
96+
}
97+
6698
func TestTonSmoke(t *testing.T) {
6799
in, err := framework.Load[CfgTon](t)
68100
require.NoError(t, err)
@@ -75,19 +107,14 @@ func TestTonSmoke(t *testing.T) {
75107
var client ton.APIClientWrapped
76108

77109
t.Run("setup:connect", func(t *testing.T) {
78-
// Create a connection pool
79-
connectionPool := liteclient.NewConnectionPool()
80-
81-
// Get the network configuration directly from the embedded config URL
82-
// The ExternalHTTPUrl already includes the full path to localhost.global.config.json
83-
cfg, cferr := liteclient.GetConfigFromUrl(t.Context(), bc.Nodes[0].ExternalHTTPUrl)
84-
require.NoError(t, cferr, "Failed to get config from URL")
85-
86-
// Add connections from the config
87-
caerr := connectionPool.AddConnectionsFromConfig(t.Context(), cfg)
88-
require.NoError(t, caerr, "Failed to add connections from config")
89-
90-
// Create an API client with retry functionality
110+
// bc.Nodes[0].ExternalHTTPUrl now contains: "liteserver://publickey@host:port"
111+
liteserverURL := bc.Nodes[0].ExternalHTTPUrl
112+
113+
// Create connection pool from liteserver URL
114+
connectionPool, err := getConnectionPoolFromLiteserverURL(t.Context(), liteserverURL)
115+
require.NoError(t, err, "Failed to create connection pool from liteserver URL")
116+
117+
// Create API client
91118
client = ton.NewAPIClient(connectionPool).WithRetry()
92119

93120
t.Run("setup:faucet", func(t *testing.T) {

framework/.changeset/v0.10.23.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fixes TON port mappping, update examples and docs

framework/components/blockchain/ton.go

Lines changed: 25 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ package blockchain
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
7-
"net/http"
8-
"net/url"
96
"strconv"
107
"time"
118

@@ -18,85 +15,28 @@ import (
1815
)
1916

2017
const (
21-
DefaultTonSimpleServerPort = "8000"
2218
// NOTE: Prefunded high-load wallet from MyLocalTon pre-funded wallet, that can send up to 254 messages per 1 external message
2319
// https://docs.ton.org/v3/documentation/smart-contracts/contracts-specs/highload-wallet#highload-wallet-v2
2420
DefaultTonHlWalletAddress = "-1:5ee77ced0b7ae6ef88ab3f4350d8872c64667ffbe76073455215d3cdfab3294b"
2521
DefaultTonHlWalletMnemonic = "twenty unfair stay entry during please water april fabric morning length lumber style tomorrow melody similar forum width ride render void rather custom coin"
26-
27-
liteServerPortOffset = 100 // internal, arbitrary offset for lite server port
22+
// internals
23+
defaultTonHTTPServerPort = "8000"
24+
defaultLiteServerPort = "40000"
25+
defaultLiteServerPublicKey = "E7XwFSQzNkcRepUC23J2nRpASXpnsEKmyyHYV4u/FZY="
26+
liteServerPortOffset = 100 // arbitrary offset for lite server port
2827
)
2928

30-
// TON config structures (e.g.: ton-blockchain.github.io/testnet-global.config.json)
31-
type tonLiteServer struct {
32-
IP int64 `json:"ip"`
33-
Port int `json:"port"`
34-
ID struct {
35-
Key string `json:"key"`
36-
Type string `json:"@type"`
37-
} `json:"id"`
38-
}
39-
40-
type tonConfig struct {
41-
LiteServers []tonLiteServer `json:"liteservers"`
42-
}
43-
44-
// convert int64 IP to string format (matches https://github.com/xssnick/tonutils-go/liteclient/connection.go/intToIP4)
45-
func intToIP4(ip int64) string {
46-
uip := uint32(ip) //nolint:gosec // IP conversion is safe for TON format
47-
return fmt.Sprintf("%d.%d.%d.%d",
48-
(uip>>24)&0xFF,
49-
(uip>>16)&0xFF,
50-
(uip>>8)&0xFF,
51-
uip&0xFF)
52-
}
53-
54-
// fetch and parse TON config to generate liteserver URLs
55-
func fetchTonConfig(configURL string) ([]string, error) {
56-
parsedURL, err := url.Parse(configURL)
57-
if err != nil {
58-
return nil, fmt.Errorf("invalid config URL: %w", err)
59-
}
60-
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
61-
return nil, fmt.Errorf("invalid URL scheme: %s", parsedURL.Scheme)
62-
}
63-
64-
resp, err := http.Get(configURL) //nolint:gosec // URL is validated above
65-
if err != nil {
66-
return nil, fmt.Errorf("failed to fetch config: %w", err)
67-
}
68-
defer resp.Body.Close()
69-
defer resp.Body.Close()
70-
71-
var config tonConfig
72-
if err := json.NewDecoder(resp.Body).Decode(&config); err != nil {
73-
return nil, fmt.Errorf("failed to decode config: %w", err)
74-
}
75-
76-
var liteServerURLs []string
77-
for _, ls := range config.LiteServers {
78-
ipStr := intToIP4(ls.IP)
79-
url := fmt.Sprintf("liteserver://%s@%s:%d", ls.ID.Key, ipStr, ls.Port)
80-
liteServerURLs = append(liteServerURLs, url)
81-
}
82-
83-
return liteServerURLs, nil
84-
}
85-
8629
type portMapping struct {
87-
SimpleServer string
88-
LiteServer string
89-
DHTServer string
90-
Console string
91-
ValidatorUDP string
30+
HTTPServer string
31+
LiteServer string
9232
}
9333

9434
func defaultTon(in *Input) {
9535
if in.Image == "" {
9636
in.Image = "ghcr.io/neodix42/mylocalton-docker:latest"
9737
}
9838
if in.Port == "" {
99-
in.Port = DefaultTonSimpleServerPort
39+
in.Port = defaultTonHTTPServerPort
10040
}
10141
}
10242

@@ -109,8 +49,8 @@ func newTon(in *Input) (*Output, error) {
10949
}
11050

11151
ports := &portMapping{
112-
SimpleServer: in.Port,
113-
LiteServer: strconv.Itoa(base + liteServerPortOffset),
52+
HTTPServer: in.Port,
53+
LiteServer: strconv.Itoa(base + liteServerPortOffset),
11454
}
11555

11656
ctx := context.Background()
@@ -125,12 +65,14 @@ func newTon(in *Input) (*Output, error) {
12565
networkName := network.Name
12666

12767
baseEnv := map[string]string{
128-
"GENESIS": "true",
129-
"NAME": "genesis",
130-
"LITE_PORT": ports.LiteServer,
131-
"CUSTOM_PARAMETERS": "--state-ttl 315360000 --archive-ttl 315360000",
68+
"GENESIS": "true",
69+
"NAME": "genesis",
70+
13271
"EMBEDDED_FILE_HTTP_SERVER": "true",
133-
"EMBEDDED_FILE_HTTP_SERVER_PORT": in.Port,
72+
"EMBEDDED_FILE_HTTP_SERVER_PORT": defaultTonHTTPServerPort,
73+
"LITE_PORT": defaultLiteServerPort,
74+
75+
"CUSTOM_PARAMETERS": "--state-ttl 315360000 --archive-ttl 315360000",
13476
}
13577

13678
// merge with additional environment variables from input
@@ -146,8 +88,8 @@ func newTon(in *Input) (*Output, error) {
14688
AlwaysPullImage: in.PullImage,
14789
Name: framework.DefaultTCName("ton-genesis"),
14890
ExposedPorts: []string{
149-
fmt.Sprintf("%s:%s/tcp", ports.SimpleServer, DefaultTonSimpleServerPort),
150-
fmt.Sprintf("%s:%s/tcp", ports.LiteServer, ports.LiteServer),
91+
fmt.Sprintf("%s:%s/tcp", ports.HTTPServer, defaultTonHTTPServerPort),
92+
fmt.Sprintf("%s:%s/tcp", ports.LiteServer, defaultLiteServerPort),
15193
"40003/udp",
15294
"40002/tcp",
15395
"40001/udp",
@@ -158,8 +100,8 @@ func newTon(in *Input) (*Output, error) {
158100
Env: finalEnv,
159101
WaitingFor: wait.ForExec([]string{
160102
"/usr/local/bin/lite-client",
161-
"-a", fmt.Sprintf("127.0.0.1:%s", ports.LiteServer),
162-
"-b", "E7XwFSQzNkcRepUC23J2nRpASXpnsEKmyyHYV4u/FZY=",
103+
"-a", fmt.Sprintf("127.0.0.1:%s", defaultLiteServerPort),
104+
"-b", defaultLiteServerPublicKey,
163105
"-t", "3", "-c", "last",
164106
}).WithStartupTimeout(2 * time.Minute),
165107
Mounts: testcontainers.ContainerMounts{
@@ -185,23 +127,16 @@ func newTon(in *Input) (*Output, error) {
185127
return nil, err
186128
}
187129

188-
name, err := c.Name(ctx)
130+
host, err := c.Host(ctx)
189131
if err != nil {
190132
return nil, err
191133
}
192134

193-
// fetch config and generate liteserver URLs from actual config
194-
configURL := fmt.Sprintf("http://localhost:%s/localhost.global.config.json", ports.SimpleServer)
195-
196-
liteServerURLs, err := fetchTonConfig(configURL)
135+
name, err := c.Name(ctx)
197136
if err != nil {
198137
return nil, err
199138
}
200139

201-
if len(liteServerURLs) == 0 {
202-
return nil, fmt.Errorf("no liteservers found in config")
203-
}
204-
205140
return &Output{
206141
UseCache: true,
207142
ChainID: in.ChainID,
@@ -211,8 +146,8 @@ func newTon(in *Input) (*Output, error) {
211146
Container: c,
212147
Nodes: []*Node{{
213148
// URLs now contain liteserver://publickey@host:port
214-
ExternalHTTPUrl: liteServerURLs[0],
215-
InternalHTTPUrl: liteServerURLs[0],
149+
ExternalHTTPUrl: fmt.Sprintf("liteserver://%s@%s:%s", defaultLiteServerPublicKey, host, ports.LiteServer),
150+
InternalHTTPUrl: fmt.Sprintf("liteserver://%s@%s:%s", defaultLiteServerPublicKey, name, ports.LiteServer),
216151
}},
217152
}, nil
218153
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[blockchain_a]
22
type = "ton"
33
image = "ghcr.io/neodix42/mylocalton-docker:v3.7"
4-
port = "8000"
4+
port = "9000"
55

66
[blockchain_a.custom_env]

0 commit comments

Comments
 (0)