Skip to content

Commit 7760d13

Browse files
authored
NONEVM-2403: TON CLDF clean up (#585)
- Reverts `*ton.APIClient` type change - Clean up CTF related method signatures to enable success/failure case test suites - Remove retry option embedded in CLDF so that caller can extend chain type flexibly
1 parent f3a2a36 commit 7760d13

6 files changed

Lines changed: 100 additions & 104 deletions

File tree

.changeset/curvy-berries-film.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": patch
3+
---
4+
5+
Clean up TON CTF Provider, update test infra methods

chain/ton/provider/ctf_provider.go

Lines changed: 86 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111
"time"
1212

1313
"github.com/avast/retry-go/v4"
14-
"github.com/stretchr/testify/require"
1514
"github.com/testcontainers/testcontainers-go"
15+
1616
"github.com/xssnick/tonutils-go/address"
1717
"github.com/xssnick/tonutils-go/tlb"
1818
"github.com/xssnick/tonutils-go/ton"
@@ -46,10 +46,6 @@ type CTFChainProviderConfig struct {
4646
// Note: Only images from supportedTONImageRepository are supported.
4747
Image string
4848

49-
// Optional: Retry count for APIClient. Default is 0 (unlimited retries).
50-
// Set to positive value for specific retry count.
51-
RetryCount int
52-
5349
// Optional: Custom environment variables to pass to the TON container.
5450
// Example: map[string]string{"NEXT_BLOCK_GENERATION_DELAY": "0.5"}
5551
CustomEnv map[string]string
@@ -61,7 +57,7 @@ func (c CTFChainProviderConfig) validate() error {
6157
return errors.New("sync.Once instance is required")
6258
}
6359

64-
if c.Image != "" && !strings.Contains(c.Image, supportedTONImageRepository) {
60+
if c.Image != "" && !strings.HasPrefix(c.Image, supportedTONImageRepository) {
6561
return fmt.Errorf("unsupported image %q: must be from %s", c.Image, supportedTONImageRepository)
6662
}
6763

@@ -99,7 +95,7 @@ func NewCTFChainProvider(
9995

10096
// Initialize sets up the Ton chain by validating the configuration, starting a CTF container,
10197
// generating a deployer signer account, and constructing the chain instance.
102-
func (p *CTFChainProvider) Initialize(_ context.Context) (chain.BlockChain, error) {
98+
func (p *CTFChainProvider) Initialize(ctx context.Context) (chain.BlockChain, error) {
10399
if p.chain != nil {
104100
return *p.chain, nil // Already initialized
105101
}
@@ -110,14 +106,28 @@ func (p *CTFChainProvider) Initialize(_ context.Context) (chain.BlockChain, erro
110106

111107
// Get the Chain ID
112108
chainID, err := chainsel.GetChainIDFromSelector(p.selector)
113-
require.NoError(p.t, err, "failed to get chain ID from selector")
109+
if err != nil {
110+
return nil, fmt.Errorf("failed to get chain ID from selector: %w", err)
111+
}
112+
113+
url, nodeClient, err := p.startContainer(ctx, chainID)
114+
if err != nil {
115+
return nil, fmt.Errorf("failed to start container: %w", err)
116+
}
114117

115-
url, nodeClient := p.startContainer(chainID)
116118
// mylocalton uses a global_id of -217 by default
117119
// https://github.com/neodix42/mylocalton-docker/blob/8f9c6ea27cd608dc6370c4191554b42b5a797905/docker/scripts/start-genesis.sh#L62
118-
tonWallet := createTonWallet(p.t, nodeClient, wallet.ConfigV5R1Final{NetworkGlobalID: -217}, wallet.WithWorkchain(0))
120+
tonWallet, err := createTonWallet(nodeClient, wallet.ConfigV5R1Final{NetworkGlobalID: -217}, wallet.WithWorkchain(0))
121+
if err != nil {
122+
return nil, fmt.Errorf("failed to create wallet: %w", err)
123+
}
124+
119125
// airdrop the deployer wallet
120-
fundTonWallets(p.t, nodeClient, []*address.Address{tonWallet.Address()}, []tlb.Coins{tlb.MustFromTON("1000")})
126+
ferr := fundTonWallets(ctx, nodeClient, []*address.Address{tonWallet.Address()}, []tlb.Coins{tlb.MustFromTON("1000")})
127+
if ferr != nil {
128+
return nil, fmt.Errorf("failed to fund wallet: %w", ferr)
129+
}
130+
121131
p.chain = &cldf_ton.Chain{
122132
ChainMetadata: cldf_ton.ChainMetadata{Selector: p.selector},
123133
Client: nodeClient,
@@ -129,15 +139,17 @@ func (p *CTFChainProvider) Initialize(_ context.Context) (chain.BlockChain, erro
129139
return *p.chain, nil
130140
}
131141

132-
func (p *CTFChainProvider) startContainer(chainID string) (string, ton.APIClientWrapped) {
142+
func (p *CTFChainProvider) startContainer(ctx context.Context, chainID string) (string, *ton.APIClient, error) {
133143
var (
134144
attempts = uint(10)
135145
url string
136146
)
137147

138148
// initialize the docker network used by CTF
139149
err := framework.DefaultNetwork(p.config.Once)
140-
require.NoError(p.t, err)
150+
if err != nil {
151+
return "", nil, fmt.Errorf("failed to initialize default network: %w", err)
152+
}
141153

142154
url, err = retry.DoWithData(func() (string, error) {
143155
// Initialize a port for the container
@@ -162,100 +174,128 @@ func (p *CTFChainProvider) startContainer(chainID string) (string, ton.APIClient
162174

163175
return output.Nodes[0].ExternalHTTPUrl, nil
164176
},
165-
retry.Context(p.t.Context()),
177+
retry.Context(ctx),
166178
retry.Attempts(attempts),
167179
retry.Delay(1*time.Second),
168180
retry.DelayType(retry.FixedDelay),
169181
retry.OnRetry(func(attempt uint, err error) {
170182
p.t.Logf("Attempt %d/%d: Failed to start CTF Ton container: %v", attempt+1, attempts, err)
171183
}),
172184
)
173-
require.NoError(p.t, err, "Failed to start CTF Ton container after %d attempts", attempts)
185+
if err != nil {
186+
return "", nil, fmt.Errorf("failed to start CTF Ton container after %d attempts: %w", attempts, err)
187+
}
174188

175-
connectionPool, err := createLiteclientConnectionPool(p.t.Context(), url)
176-
require.NoError(p.t, err)
189+
connectionPool, err := createLiteclientConnectionPool(ctx, url)
190+
if err != nil {
191+
return "", nil, fmt.Errorf("failed to create liteclient connection pool: %w", err)
192+
}
177193

178194
client := ton.NewAPIClient(connectionPool, ton.ProofCheckPolicyFast)
179195

180196
// check connection, CTFv2 handles the readiness
181-
mb := getMasterchainBlockID(p.t, client)
197+
mb, err := getMasterchainBlockID(ctx, client)
198+
if err != nil {
199+
return "", nil, fmt.Errorf("failed to get masterchain block ID: %w", err)
200+
}
201+
182202
// set starting point to verify master block proofs chain
183203
client.SetTrustedBlock(mb)
184204

185-
retryCount := p.getRetryCount()
186-
187-
return url, client.WithRetry(retryCount)
205+
return url, client, nil
188206
}
189207

190208
// Note: this utility functions can be replaced once we have in the chainlink-ton utils package
191-
func createTonWallet(t *testing.T, client ton.APIClientWrapped, versionConfig wallet.VersionConfig, option wallet.Option) *wallet.Wallet {
192-
t.Helper()
193-
209+
func createTonWallet(client ton.APIClientWrapped, versionConfig wallet.VersionConfig, option wallet.Option) (*wallet.Wallet, error) {
194210
seed := wallet.NewSeed()
195211
rw, err := wallet.FromSeed(client, seed, versionConfig)
196-
require.NoError(t, err)
212+
if err != nil {
213+
return nil, fmt.Errorf("failed to create wallet from seed: %w", err)
214+
}
197215
pw, perr := wallet.FromPrivateKeyWithOptions(client, rw.PrivateKey(), versionConfig, option)
198-
require.NoError(t, perr)
216+
if perr != nil {
217+
return nil, fmt.Errorf("failed to create wallet from private key: %w", perr)
218+
}
199219

200-
return pw
220+
return pw, nil
201221
}
202222

203-
func fundTonWallets(t *testing.T, client ton.APIClientWrapped, recipients []*address.Address, amounts []tlb.Coins) {
204-
t.Helper()
223+
func fundTonWallets(ctx context.Context, client ton.APIClientWrapped, recipients []*address.Address, amounts []tlb.Coins) error {
224+
if len(amounts) != len(recipients) {
225+
return errors.New("recipients and amounts must have the same length")
226+
}
205227

206-
require.Len(t, amounts, len(recipients), "recipients and amounts must have the same length")
207228
// initialize the prefunded wallet(Highload-V2), for other wallets, see https://github.com/neodix42/mylocalton-docker#pre-installed-wallets
208229
version := wallet.HighloadV2Verified //nolint:staticcheck // SA1019: only available option in mylocalton-docker
209230
rawHlWallet, err := wallet.FromSeed(client, strings.Fields(blockchain.DefaultTonHlWalletMnemonic), version)
210-
require.NoError(t, err)
231+
if err != nil {
232+
return fmt.Errorf("failed to create wallet from seed: %w", err)
233+
}
211234

212235
mcFunderWallet, err := wallet.FromPrivateKeyWithOptions(client, rawHlWallet.PrivateKey(), version, wallet.WithWorkchain(-1))
213-
require.NoError(t, err)
236+
if err != nil {
237+
return fmt.Errorf("failed to create wallet from private key: %w", err)
238+
}
214239

215240
funder, err := mcFunderWallet.GetSubwallet(uint32(42))
216-
require.NoError(t, err)
241+
if err != nil {
242+
return fmt.Errorf("failed to get subwallet: %w", err)
243+
}
244+
217245
// double check funder address
218-
require.Equal(t, blockchain.DefaultTonHlWalletAddress, funder.Address().StringRaw(), "funder address mismatch")
246+
if funder.Address().StringRaw() != blockchain.DefaultTonHlWalletAddress {
247+
return fmt.Errorf("funder address mismatch: %s != %s", funder.Address().StringRaw(), blockchain.DefaultTonHlWalletAddress)
248+
}
249+
219250
// create transfer messages for each recipient
220251
messages := make([]*wallet.Message, len(recipients))
221252
for i, addr := range recipients {
222253
transfer, terr := funder.BuildTransfer(addr, amounts[i], false, "")
223-
require.NoError(t, terr)
254+
if terr != nil {
255+
return fmt.Errorf("failed to build transfer: %w", terr)
256+
}
224257
messages[i] = transfer
225258
}
226-
_, _, txerr := funder.SendManyWaitTransaction(t.Context(), messages)
227-
require.NoError(t, txerr, "airdrop transaction failed")
259+
228260
// we don't wait for the transaction to be confirmed here, as it may take some time
229-
}
261+
// the name SendManyWaitTransaction is misleading, it doesn't wait for the transaction to be confirmed,
262+
// it just sends the transactions(TON has asynchronous transactions)
263+
_, _, txerr := funder.SendManyWaitTransaction(ctx, messages)
264+
if txerr != nil {
265+
return fmt.Errorf("failed to send many wait transaction: %w", txerr)
266+
}
230267

231-
func getMasterchainBlockID(t *testing.T, client *ton.APIClient) *ton.BlockIDExt {
232-
t.Helper()
268+
return nil
269+
}
233270

271+
func getMasterchainBlockID(ctx context.Context, client ton.APIClientWrapped) (*ton.BlockIDExt, error) {
234272
var masterchainBlockID *ton.BlockIDExt
235273
// check connection, CTFv2 handles the readiness
236274
err := retry.Do(func() error {
237275
var err error
238-
masterchainBlockID, err = client.GetMasterchainInfo(t.Context())
276+
masterchainBlockID, err = client.GetMasterchainInfo(ctx)
239277

240278
return err
241279
},
242-
retry.Context(t.Context()),
280+
retry.Context(ctx),
243281
retry.Attempts(30),
244282
retry.Delay(1*time.Second),
245283
retry.DelayType(retry.FixedDelay),
246284
)
247-
require.NoError(t, err, "TON network not ready")
285+
if err != nil {
286+
return nil, fmt.Errorf("failed to get masterchain info: %w", err)
287+
}
248288

249289
// return masterchain block for setting trusted block
250-
return masterchainBlockID
290+
return masterchainBlockID, nil
251291
}
252292

253293
// Name returns the name of the CTFChainProvider.
254294
func (*CTFChainProvider) Name() string {
255-
return "Ton CTF Chain Provider"
295+
return "TON CTF Chain Provider"
256296
}
257297

258-
// ChainSelector returns the chain selector of the Aptos chain managed by this provider.
298+
// ChainSelector returns the chain selector of the TON chain managed by this provider.
259299
func (p *CTFChainProvider) ChainSelector() uint64 {
260300
return p.selector
261301
}
@@ -266,10 +306,6 @@ func (p *CTFChainProvider) BlockChain() chain.BlockChain {
266306
return *p.chain
267307
}
268308

269-
func (p *CTFChainProvider) getRetryCount() int {
270-
return p.config.RetryCount
271-
}
272-
273309
// getImage returns the configured Docker image, or the default if not specified.
274310
func (p *CTFChainProvider) getImage() string {
275311
if p.config.Image != "" {

chain/ton/provider/ctf_provider_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func Test_CTFChainProvider_Name(t *testing.T) {
9999
t.Parallel()
100100

101101
p := &CTFChainProvider{}
102-
assert.Equal(t, "Ton CTF Chain Provider", p.Name())
102+
assert.Equal(t, "TON CTF Chain Provider", p.Name())
103103
}
104104

105105
func Test_CTFChainProvider_ChainSelector(t *testing.T) {

chain/ton/provider/rpc_provider.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ 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
4239
}
4340

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

115112
// setupConnection creates and tests a connection to the TON liteserver
116-
func setupConnection(ctx context.Context, liteserverURL string, retryCount int) (tonlib.APIClientWrapped, error) {
113+
func setupConnection(ctx context.Context, liteserverURL string) (*tonlib.APIClient, error) {
117114
connectionPool, err := createLiteclientConnectionPool(ctx, liteserverURL)
118115
if err != nil {
119116
return nil, fmt.Errorf("failed to connect to liteserver: %w", err)
@@ -130,7 +127,7 @@ func setupConnection(ctx context.Context, liteserverURL string, retryCount int)
130127
// Set starting point to verify master block proofs chain
131128
api.SetTrustedBlock(mb)
132129

133-
return api.WithRetry(retryCount), nil
130+
return api, nil
134131
}
135132

136133
// createWallet creates a TON wallet from the given private key and API client
@@ -159,7 +156,7 @@ func (p *RPCChainProvider) Initialize(ctx context.Context) (chain.BlockChain, er
159156
}
160157

161158
// Setup connection to TON network
162-
api, err := setupConnection(ctx, p.config.HTTPURL, p.getRetryCount())
159+
api, err := setupConnection(ctx, p.config.HTTPURL)
163160
if err != nil {
164161
return nil, err
165162
}
@@ -242,7 +239,3 @@ func (p *RPCChainProvider) ChainSelector() uint64 {
242239
func (p *RPCChainProvider) BlockChain() chain.BlockChain {
243240
return *p.chain
244241
}
245-
246-
func (p *RPCChainProvider) getRetryCount() int {
247-
return p.config.RetryCount
248-
}

chain/ton/provider/rpc_provider_test.go

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -223,42 +223,6 @@ func Test_getWalletVersionConfig(t *testing.T) {
223223
}
224224
}
225225

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-
262226
func Test_NewRPCChainProvider(t *testing.T) {
263227
t.Parallel()
264228

@@ -267,7 +231,6 @@ func Test_NewRPCChainProvider(t *testing.T) {
267231
HTTPURL: "liteserver://publickey@localhost:8080",
268232
DeployerSignerGen: PrivateKeyRandom(),
269233
WalletVersion: WalletVersionV5R1,
270-
RetryCount: 10,
271234
}
272235

273236
p := NewRPCChainProvider(selector, config)
@@ -276,7 +239,6 @@ func Test_NewRPCChainProvider(t *testing.T) {
276239
assert.Equal(t, selector, p.selector)
277240
assert.Equal(t, config.HTTPURL, p.config.HTTPURL)
278241
assert.Equal(t, config.WalletVersion, p.config.WalletVersion)
279-
assert.Equal(t, config.RetryCount, p.config.RetryCount)
280242
assert.Nil(t, p.chain)
281243
}
282244

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.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
15+
ChainMetadata // Contains canonical chain identifier
16+
Client *ton.APIClient // 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)