Skip to content

Commit 0baab99

Browse files
authored
NONEVM-2403: Pass through mylocalton configs (#580)
For bumping TON localnet version w/o updating frameworks smartcontractkit/chainlink-ton#373 - Added configurations for CTFv2, that can be set by caller - Support ton api client interface
1 parent f7a31c2 commit 0baab99

6 files changed

Lines changed: 216 additions & 19 deletions

File tree

.changeset/good-cups-invite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": minor
3+
---
4+
5+
expose TON CTF configs to caller

chain/ton/provider/ctf_provider.go

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,48 @@ import (
1111
"time"
1212

1313
"github.com/avast/retry-go/v4"
14-
chainsel "github.com/smartcontractkit/chain-selectors"
15-
"github.com/smartcontractkit/chainlink-testing-framework/framework"
16-
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
17-
"github.com/smartcontractkit/freeport"
1814
"github.com/stretchr/testify/require"
1915
"github.com/testcontainers/testcontainers-go"
2016
"github.com/xssnick/tonutils-go/address"
2117
"github.com/xssnick/tonutils-go/tlb"
2218
"github.com/xssnick/tonutils-go/ton"
2319
"github.com/xssnick/tonutils-go/ton/wallet"
2420

21+
chainsel "github.com/smartcontractkit/chain-selectors"
22+
"github.com/smartcontractkit/chainlink-testing-framework/framework"
23+
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
24+
"github.com/smartcontractkit/freeport"
25+
2526
"github.com/smartcontractkit/chainlink-deployments-framework/chain"
2627
cldf_ton "github.com/smartcontractkit/chainlink-deployments-framework/chain/ton"
2728
)
2829

30+
const (
31+
// defaultTONImage is the default Docker image used for TON localnet.
32+
// Only images from this repository are supported.
33+
defaultTONImage = "ghcr.io/neodix42/mylocalton-docker:v3.7"
34+
35+
// supportedTONImageRepository is the only supported Docker image repository for TON localnet.
36+
supportedTONImageRepository = "ghcr.io/neodix42/mylocalton-docker"
37+
)
38+
2939
// CTFChainProviderConfig holds the configuration to initialize the CTFChainProvider.
3040
type CTFChainProviderConfig struct {
3141
// Required: A sync.Once instance to ensure that the CTF framework only sets up the new
3242
// DefaultNetwork once
3343
Once *sync.Once
44+
45+
// Optional: Docker image to use for the TON localnet. If empty, defaults to defaultTONImage.
46+
// Note: Only images from supportedTONImageRepository are supported.
47+
Image string
48+
49+
// Optional: Retry count for APIClient. Default is 0 (unlimited retries).
50+
// Set to positive value for specific retry count.
51+
RetryCount int
52+
53+
// Optional: Custom environment variables to pass to the TON container.
54+
// Example: map[string]string{"NEXT_BLOCK_GENERATION_DELAY": "0.5"}
55+
CustomEnv map[string]string
3456
}
3557

3658
// validate checks if the CTFChainProviderConfig is valid.
@@ -39,6 +61,10 @@ func (c CTFChainProviderConfig) validate() error {
3961
return errors.New("sync.Once instance is required")
4062
}
4163

64+
if c.Image != "" && !strings.Contains(c.Image, supportedTONImageRepository) {
65+
return fmt.Errorf("unsupported image %q: must be from %s", c.Image, supportedTONImageRepository)
66+
}
67+
4268
return nil
4369
}
4470

@@ -103,7 +129,7 @@ func (p *CTFChainProvider) Initialize(_ context.Context) (chain.BlockChain, erro
103129
return *p.chain, nil
104130
}
105131

106-
func (p *CTFChainProvider) startContainer(chainID string) (string, *ton.APIClient) {
132+
func (p *CTFChainProvider) startContainer(chainID string) (string, ton.APIClientWrapped) {
107133
var (
108134
attempts = uint(10)
109135
url string
@@ -119,10 +145,11 @@ func (p *CTFChainProvider) startContainer(chainID string) (string, *ton.APIClien
119145

120146
// spin up mylocalton with CTFv2
121147
output, rerr := blockchain.NewBlockchainNetwork(&blockchain.Input{
122-
Type: blockchain.TypeTon,
123-
ChainID: chainID,
124-
Port: strconv.Itoa(port),
125-
Image: "ghcr.io/neodix42/mylocalton-docker:v3.7",
148+
Type: blockchain.TypeTon,
149+
ChainID: chainID,
150+
Port: strconv.Itoa(port),
151+
Image: p.getImage(),
152+
CustomEnv: p.config.CustomEnv,
126153
})
127154
if rerr != nil {
128155
// Return the ports to freeport to avoid leaking them during retries
@@ -155,7 +182,9 @@ func (p *CTFChainProvider) startContainer(chainID string) (string, *ton.APIClien
155182
// set starting point to verify master block proofs chain
156183
client.SetTrustedBlock(mb)
157184

158-
return url, client
185+
retryCount := p.getRetryCount()
186+
187+
return url, client.WithRetry(retryCount)
159188
}
160189

161190
// Note: this utility functions can be replaced once we have in the chainlink-ton utils package
@@ -236,3 +265,16 @@ func (p *CTFChainProvider) ChainSelector() uint64 {
236265
func (p *CTFChainProvider) BlockChain() chain.BlockChain {
237266
return *p.chain
238267
}
268+
269+
func (p *CTFChainProvider) getRetryCount() int {
270+
return p.config.RetryCount
271+
}
272+
273+
// getImage returns the configured Docker image, or the default if not specified.
274+
func (p *CTFChainProvider) getImage() string {
275+
if p.config.Image != "" {
276+
return p.config.Image
277+
}
278+
279+
return defaultTONImage
280+
}

chain/ton/provider/ctf_provider_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,15 @@ func Test_CTFChainProvider_BlockChain(t *testing.T) {
120120

121121
assert.Equal(t, *chain, p.BlockChain())
122122
}
123+
124+
func Test_CTFChainProvider_getImage(t *testing.T) {
125+
t.Parallel()
126+
127+
// Test default image
128+
p1 := &CTFChainProvider{config: CTFChainProviderConfig{}}
129+
assert.Equal(t, "ghcr.io/neodix42/mylocalton-docker:v3.7", p1.getImage())
130+
131+
// Test custom image
132+
p2 := &CTFChainProvider{config: CTFChainProviderConfig{Image: "ghcr.io/neodix42/mylocalton-docker:latest"}}
133+
assert.Equal(t, "ghcr.io/neodix42/mylocalton-docker:latest", p2.getImage())
134+
}

chain/ton/provider/rpc_provider.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ type RPCChainProviderConfig struct {
3636
// Optional: The TON wallet version to use. Supported versions are: V1R1, V1R2, V1R3, V2R1,
3737
// V2R2, V3R1, V3R2, V4R1, V4R2 and V5R1. If no value provided, V5R1 is used as default.
3838
WalletVersion WalletVersion
39+
// Optional: Retry count for APIClient. Default is 0 (unlimited retries).
40+
// Set to positive value for specific retry count.
41+
RetryCount int
3942
}
4043

4144
// validateLiteserverURL validates the format of a liteserver URL
@@ -110,7 +113,7 @@ func NewRPCChainProvider(selector uint64, config RPCChainProviderConfig) *RPCCha
110113
}
111114

112115
// setupConnection creates and tests a connection to the TON liteserver
113-
func setupConnection(ctx context.Context, liteserverURL string) (*tonlib.APIClient, error) {
116+
func setupConnection(ctx context.Context, liteserverURL string, retryCount int) (tonlib.APIClientWrapped, error) {
114117
connectionPool, err := createLiteclientConnectionPool(ctx, liteserverURL)
115118
if err != nil {
116119
return nil, fmt.Errorf("failed to connect to liteserver: %w", err)
@@ -127,11 +130,11 @@ func setupConnection(ctx context.Context, liteserverURL string) (*tonlib.APIClie
127130
// Set starting point to verify master block proofs chain
128131
api.SetTrustedBlock(mb)
129132

130-
return api, nil
133+
return api.WithRetry(retryCount), nil
131134
}
132135

133136
// createWallet creates a TON wallet from the given private key and API client
134-
func createWallet(api *tonlib.APIClient, privateKey []byte, version WalletVersion) (*wallet.Wallet, error) {
137+
func createWallet(api tonlib.APIClientWrapped, privateKey []byte, version WalletVersion) (*wallet.Wallet, error) {
135138
walletConfig, err := getWalletVersionConfig(version)
136139
if err != nil {
137140
return nil, fmt.Errorf("unsupported wallet version: %w", err)
@@ -156,7 +159,7 @@ func (p *RPCChainProvider) Initialize(ctx context.Context) (chain.BlockChain, er
156159
}
157160

158161
// Setup connection to TON network
159-
api, err := setupConnection(ctx, p.config.HTTPURL)
162+
api, err := setupConnection(ctx, p.config.HTTPURL, p.getRetryCount())
160163
if err != nil {
161164
return nil, err
162165
}
@@ -239,3 +242,7 @@ func (p *RPCChainProvider) ChainSelector() uint64 {
239242
func (p *RPCChainProvider) BlockChain() chain.BlockChain {
240243
return *p.chain
241244
}
245+
246+
func (p *RPCChainProvider) getRetryCount() int {
247+
return p.config.RetryCount
248+
}

chain/ton/provider/rpc_provider_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,134 @@ func Test_getWalletVersionConfig(t *testing.T) {
222222
})
223223
}
224224
}
225+
226+
func Test_RPCChainProvider_getRetryCount(t *testing.T) {
227+
t.Parallel()
228+
229+
tests := []struct {
230+
name string
231+
retryCount int
232+
want int
233+
}{
234+
{
235+
name: "returns configured retry count",
236+
retryCount: 10,
237+
want: 10,
238+
},
239+
{
240+
name: "returns zero for unlimited retries",
241+
retryCount: 0,
242+
want: 0,
243+
},
244+
}
245+
246+
for _, tt := range tests {
247+
t.Run(tt.name, func(t *testing.T) {
248+
t.Parallel()
249+
250+
p := &RPCChainProvider{
251+
config: RPCChainProviderConfig{
252+
RetryCount: tt.retryCount,
253+
},
254+
}
255+
256+
got := p.getRetryCount()
257+
assert.Equal(t, tt.want, got)
258+
})
259+
}
260+
}
261+
262+
func Test_NewRPCChainProvider(t *testing.T) {
263+
t.Parallel()
264+
265+
selector := uint64(12345)
266+
config := RPCChainProviderConfig{
267+
HTTPURL: "liteserver://publickey@localhost:8080",
268+
DeployerSignerGen: PrivateKeyRandom(),
269+
WalletVersion: WalletVersionV5R1,
270+
RetryCount: 10,
271+
}
272+
273+
p := NewRPCChainProvider(selector, config)
274+
275+
require.NotNil(t, p)
276+
assert.Equal(t, selector, p.selector)
277+
assert.Equal(t, config.HTTPURL, p.config.HTTPURL)
278+
assert.Equal(t, config.WalletVersion, p.config.WalletVersion)
279+
assert.Equal(t, config.RetryCount, p.config.RetryCount)
280+
assert.Nil(t, p.chain)
281+
}
282+
283+
func Test_createWallet(t *testing.T) {
284+
t.Parallel()
285+
286+
tests := []struct {
287+
name string
288+
version WalletVersion
289+
expectError bool
290+
errorContains string
291+
}{
292+
{
293+
name: "unsupported wallet version returns error",
294+
version: "V9R9",
295+
expectError: true,
296+
errorContains: "unsupported wallet version",
297+
},
298+
}
299+
300+
for _, tt := range tests {
301+
t.Run(tt.name, func(t *testing.T) {
302+
t.Parallel()
303+
304+
privateKey := make([]byte, 32)
305+
wallet, err := createWallet(nil, privateKey, tt.version)
306+
307+
if tt.expectError {
308+
require.Error(t, err)
309+
assert.Contains(t, err.Error(), tt.errorContains)
310+
assert.Nil(t, wallet)
311+
} else {
312+
require.NoError(t, err)
313+
assert.NotNil(t, wallet)
314+
}
315+
})
316+
}
317+
}
318+
319+
func Test_createLiteclientConnectionPool_InvalidURL(t *testing.T) {
320+
t.Parallel()
321+
322+
tests := []struct {
323+
name string
324+
url string
325+
errorContains string
326+
}{
327+
{
328+
name: "empty URL returns error",
329+
url: "",
330+
errorContains: "liteserver url is required",
331+
},
332+
{
333+
name: "invalid prefix returns error",
334+
url: "http://example.com",
335+
errorContains: "invalid liteserver URL format",
336+
},
337+
{
338+
name: "missing @ returns error",
339+
url: "liteserver://invalidurl",
340+
errorContains: "invalid liteserver URL format",
341+
},
342+
}
343+
344+
for _, tt := range tests {
345+
t.Run(tt.name, func(t *testing.T) {
346+
t.Parallel()
347+
348+
pool, err := createLiteclientConnectionPool(t.Context(), tt.url)
349+
350+
require.Error(t, err)
351+
assert.Contains(t, err.Error(), tt.errorContains)
352+
assert.Nil(t, pool)
353+
})
354+
}
355+
}

chain/ton/ton_chain.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ type ChainMetadata = common.ChainMetadata
1212

1313
// Chain represents a TON chain.
1414
type Chain struct {
15-
ChainMetadata // Contains canonical chain identifier
16-
Client *ton.APIClient // RPC client via Lite Server
17-
Wallet *wallet.Wallet // Wallet abstraction (signing, sending)
18-
WalletAddress *address.Address // Address of deployer wallet
19-
URL string // Liteserver URL
15+
ChainMetadata // Contains canonical chain identifier
16+
Client ton.APIClientWrapped // APIClient for Lite Server connection
17+
Wallet *wallet.Wallet // Wallet abstraction (signing, sending)
18+
WalletAddress *address.Address // Address of deployer wallet
19+
URL string // Liteserver URL
2020
}

0 commit comments

Comments
 (0)