Skip to content

Commit 53de762

Browse files
committed
feat: ton support
1 parent e109695 commit 53de762

File tree

11 files changed

+1320
-202
lines changed

11 files changed

+1320
-202
lines changed

.github/workflows/framework-golden-tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ jobs:
4343
config: smoke_solana.toml
4444
count: 1
4545
timeout: 10m
46+
- name: TestTonSmoke
47+
config: smoke_ton.toml
48+
count: 1
49+
timeout: 10m
4650
- name: TestUpgrade
4751
config: upgrade.toml
4852
count: 1

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
- [Sui](framework/components/blockchains/sui.md)
4747
- [TRON](framework/components/blockchains/tron.md)
4848
- [ZKSync](framework/components/blockchains/zksync.md)
49+
- [Ton](framework/components/blockchains/ton.md)
4950
- [Optimism Stack]()
5051
- [Arbitrum Stack]()
5152
- [Chainlink](framework/components/chainlink.md)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<!-- todo: add ton blockchain doc -->

framework/components/blockchain/blockchain.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818
TypeAptos = "aptos"
1919
TypeSui = "sui"
2020
TypeTron = "tron"
21+
TypeTon = "ton"
2122
)
2223

2324
// Blockchain node family
@@ -27,12 +28,13 @@ const (
2728
FamilyAptos = "aptos"
2829
FamilySui = "sui"
2930
FamilyTron = "tron"
31+
FamilyTon = "ton"
3032
)
3133

3234
// Input is a blockchain network configuration params
3335
type Input struct {
3436
// Common EVM fields
35-
Type string `toml:"type" validate:"required,oneof=anvil geth besu solana aptos tron sui" envconfig:"net_type"`
37+
Type string `toml:"type" validate:"required,oneof=anvil geth besu solana aptos tron sui ton" envconfig:"net_type"`
3638
Image string `toml:"image"`
3739
PullImage bool `toml:"pull_image"`
3840
Port string `toml:"port"`
@@ -52,6 +54,9 @@ type Input struct {
5254
SolanaPrograms map[string]string `toml:"solana_programs"`
5355
ContainerResources *framework.ContainerResources `toml:"resources"`
5456
CustomPorts []string `toml:"custom_ports"`
57+
58+
// Ton
59+
CoreServices []string `toml:"core_services"`
5560
}
5661

5762
// Output is a blockchain network output, ChainID and one or more nodes that forms the network
@@ -67,7 +72,8 @@ type Output struct {
6772
}
6873

6974
type NetworkSpecificData struct {
70-
SuiAccount *SuiWalletInfo
75+
SuiAccount *SuiWalletInfo
76+
TonGlobalConfigURL string `toml:"global_config_url"`
7177
}
7278

7379
// Node represents blockchain node output, URLs required for connection locally and inside docker network
@@ -99,6 +105,8 @@ func NewBlockchainNetwork(in *Input) (*Output, error) {
99105
out, err = newTron(in)
100106
case TypeAnvilZKSync:
101107
out, err = newAnvilZksync(in)
108+
case TypeTon:
109+
out, err = newTon(in)
102110
default:
103111
return nil, fmt.Errorf("blockchain type is not supported or empty, must be 'anvil' or 'geth'")
104112
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package blockchain
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"os"
9+
"path/filepath"
10+
"time"
11+
12+
"github.com/docker/go-connections/nat"
13+
"github.com/smartcontractkit/chainlink-testing-framework/framework"
14+
15+
"github.com/testcontainers/testcontainers-go/modules/compose"
16+
"github.com/testcontainers/testcontainers-go/wait"
17+
)
18+
19+
const (
20+
// default ports from mylocalton-docker
21+
DefaultTonHTTPAPIPort = "8081"
22+
DefaultTonSimpleServerPort = "8000"
23+
DefaultTonTONExplorerPort = "8080"
24+
DefaultTonLiteServerPort = "40004"
25+
)
26+
27+
func defaultTon(in *Input) {
28+
if in.Image == "" {
29+
in.Image = "neodix42/mylocalton-docker:latest"
30+
}
31+
if in.Port != "" {
32+
framework.L.Warn().Msgf("'port' field is set but only default port can be used: %s", DefaultTonHTTPAPIPort)
33+
}
34+
in.Port = DefaultTonHTTPAPIPort
35+
}
36+
37+
func newTon(in *Input) (*Output, error) {
38+
defaultTon(in)
39+
containerName := framework.DefaultTCName("blockchain-node")
40+
41+
resp, err := http.Get("https://raw.githubusercontent.com/neodix42/mylocalton-docker/main/docker-compose.yaml")
42+
if err != nil {
43+
return nil, fmt.Errorf("failed to download docker-compose file: %v", err)
44+
}
45+
defer resp.Body.Close()
46+
47+
tempDir, err := os.MkdirTemp(".", "ton-mylocalton-docker")
48+
if err != nil {
49+
return nil, fmt.Errorf("failed to create temp directory: %v", err)
50+
}
51+
52+
defer func() {
53+
// delete the folder whether it was successful or not
54+
_ = os.RemoveAll(tempDir)
55+
}()
56+
57+
composeFile := filepath.Join(tempDir, "docker-compose.yaml")
58+
file, err := os.Create(composeFile)
59+
if err != nil {
60+
return nil, fmt.Errorf("failed to create compose file: %v", err)
61+
}
62+
63+
_, err = io.Copy(file, resp.Body)
64+
if err != nil {
65+
file.Close()
66+
return nil, fmt.Errorf("failed to write compose file: %v", err)
67+
}
68+
file.Close()
69+
70+
ctx := context.Background()
71+
72+
var stack compose.ComposeStack
73+
stack, err = compose.NewDockerComposeWith(
74+
compose.WithStackFiles(composeFile),
75+
compose.StackIdentifier(containerName),
76+
)
77+
if err != nil {
78+
return nil, fmt.Errorf("failed to create compose stack: %v", err)
79+
}
80+
81+
var upOpts []compose.StackUpOption
82+
83+
// always wait for healthy
84+
upOpts = append(upOpts, compose.Wait(true))
85+
services := in.CoreServices
86+
if os.Getenv("CI") == "true" && len(services) == 0 {
87+
services = []string{
88+
"genesis", "tonhttpapi", "event-cache",
89+
"index-postgres", "index-worker", "index-api",
90+
}
91+
}
92+
93+
if len(services) > 0 {
94+
upOpts = append(upOpts, compose.RunServices(services...))
95+
}
96+
97+
const genesisBlockID = "E7XwFSQzNkcRepUC23J2nRpASXpnsEKmyyHYV4u/FZY="
98+
execStrat := wait.ForExec([]string{
99+
"/usr/local/bin/lite-client",
100+
"-a", "127.0.0.1:" + DefaultTonLiteServerPort,
101+
"-b", genesisBlockID,
102+
"-t", "3",
103+
"-c", "last",
104+
}).
105+
WithPollInterval(5 * time.Second).
106+
WithStartupTimeout(180 * time.Second)
107+
108+
stack = stack.
109+
WaitForService("genesis", execStrat).
110+
WaitForService("tonhttpapi", wait.ForListeningPort(DefaultTonHTTPAPIPort+"/tcp"))
111+
112+
if err := stack.Up(ctx, upOpts...); err != nil {
113+
return nil, fmt.Errorf("failed to start compose stack: %w", err)
114+
}
115+
cfgCtr, _ := stack.ServiceContainer(ctx, "genesis")
116+
cfgHost, _ := cfgCtr.Host(ctx)
117+
cfgPort, _ := cfgCtr.MappedPort(ctx, nat.Port("8000/tcp"))
118+
globalCfgURL := fmt.Sprintf("http://%s:%s/localhost.global.config.json", cfgHost, cfgPort.Port())
119+
120+
// discover lite‐server addr
121+
liteCtr, _ := stack.ServiceContainer(ctx, "genesis")
122+
liteHost, _ := liteCtr.Host(ctx)
123+
litePort, _ := liteCtr.MappedPort(ctx, nat.Port("40004/tcp"))
124+
125+
return &Output{
126+
UseCache: true,
127+
ChainID: in.ChainID,
128+
Type: in.Type,
129+
Family: FamilyTon,
130+
ContainerName: containerName,
131+
Nodes: []*Node{{
132+
// todo: do we need more access?
133+
ExternalHTTPUrl: fmt.Sprintf("%s:%s", liteHost, litePort.Port()),
134+
}},
135+
NetworkSpecificData: &NetworkSpecificData{
136+
TonGlobalConfigURL: globalCfgURL,
137+
},
138+
}, nil
139+
}

0 commit comments

Comments
 (0)