-
Notifications
You must be signed in to change notification settings - Fork 3
NONEVM-2403: TON CLDF clean up #585
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "chainlink-deployments-framework": patch | ||
| --- | ||
|
|
||
| Clean up TON CTF Provider, update test infra methods |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,8 +11,8 @@ import ( | |
| "time" | ||
|
|
||
| "github.com/avast/retry-go/v4" | ||
| "github.com/stretchr/testify/require" | ||
| "github.com/testcontainers/testcontainers-go" | ||
|
|
||
| "github.com/xssnick/tonutils-go/address" | ||
| "github.com/xssnick/tonutils-go/tlb" | ||
| "github.com/xssnick/tonutils-go/ton" | ||
|
|
@@ -46,10 +46,6 @@ type CTFChainProviderConfig struct { | |
| // Note: Only images from supportedTONImageRepository are supported. | ||
| Image string | ||
|
|
||
| // Optional: Retry count for APIClient. Default is 0 (unlimited retries). | ||
| // Set to positive value for specific retry count. | ||
| RetryCount int | ||
|
|
||
| // Optional: Custom environment variables to pass to the TON container. | ||
| // Example: map[string]string{"NEXT_BLOCK_GENERATION_DELAY": "0.5"} | ||
| CustomEnv map[string]string | ||
|
|
@@ -61,7 +57,7 @@ func (c CTFChainProviderConfig) validate() error { | |
| return errors.New("sync.Once instance is required") | ||
| } | ||
|
|
||
| if c.Image != "" && !strings.Contains(c.Image, supportedTONImageRepository) { | ||
| if c.Image != "" && !strings.HasPrefix(c.Image, supportedTONImageRepository) { | ||
| return fmt.Errorf("unsupported image %q: must be from %s", c.Image, supportedTONImageRepository) | ||
| } | ||
|
|
||
|
|
@@ -99,7 +95,7 @@ func NewCTFChainProvider( | |
|
|
||
| // Initialize sets up the Ton chain by validating the configuration, starting a CTF container, | ||
| // generating a deployer signer account, and constructing the chain instance. | ||
| func (p *CTFChainProvider) Initialize(_ context.Context) (chain.BlockChain, error) { | ||
| func (p *CTFChainProvider) Initialize(ctx context.Context) (chain.BlockChain, error) { | ||
| if p.chain != nil { | ||
| return *p.chain, nil // Already initialized | ||
| } | ||
|
|
@@ -110,14 +106,28 @@ func (p *CTFChainProvider) Initialize(_ context.Context) (chain.BlockChain, erro | |
|
|
||
| // Get the Chain ID | ||
| chainID, err := chainsel.GetChainIDFromSelector(p.selector) | ||
| require.NoError(p.t, err, "failed to get chain ID from selector") | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to get chain ID from selector: %w", err) | ||
| } | ||
|
|
||
| url, nodeClient, err := p.startContainer(ctx, chainID) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to start container: %w", err) | ||
| } | ||
|
|
||
| url, nodeClient := p.startContainer(chainID) | ||
| // mylocalton uses a global_id of -217 by default | ||
| // https://github.com/neodix42/mylocalton-docker/blob/8f9c6ea27cd608dc6370c4191554b42b5a797905/docker/scripts/start-genesis.sh#L62 | ||
| tonWallet := createTonWallet(p.t, nodeClient, wallet.ConfigV5R1Final{NetworkGlobalID: -217}, wallet.WithWorkchain(0)) | ||
| tonWallet, err := createTonWallet(nodeClient, wallet.ConfigV5R1Final{NetworkGlobalID: -217}, wallet.WithWorkchain(0)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to create wallet: %w", err) | ||
| } | ||
|
|
||
| // airdrop the deployer wallet | ||
| fundTonWallets(p.t, nodeClient, []*address.Address{tonWallet.Address()}, []tlb.Coins{tlb.MustFromTON("1000")}) | ||
| ferr := fundTonWallets(ctx, nodeClient, []*address.Address{tonWallet.Address()}, []tlb.Coins{tlb.MustFromTON("1000")}) | ||
| if ferr != nil { | ||
| return nil, fmt.Errorf("failed to fund wallet: %w", ferr) | ||
| } | ||
|
|
||
| p.chain = &cldf_ton.Chain{ | ||
| ChainMetadata: cldf_ton.ChainMetadata{Selector: p.selector}, | ||
| Client: nodeClient, | ||
|
|
@@ -129,15 +139,17 @@ func (p *CTFChainProvider) Initialize(_ context.Context) (chain.BlockChain, erro | |
| return *p.chain, nil | ||
| } | ||
|
|
||
| func (p *CTFChainProvider) startContainer(chainID string) (string, ton.APIClientWrapped) { | ||
| func (p *CTFChainProvider) startContainer(ctx context.Context, chainID string) (string, *ton.APIClient, error) { | ||
| var ( | ||
| attempts = uint(10) | ||
| url string | ||
| ) | ||
|
|
||
| // initialize the docker network used by CTF | ||
| err := framework.DefaultNetwork(p.config.Once) | ||
| require.NoError(p.t, err) | ||
| if err != nil { | ||
| return "", nil, fmt.Errorf("failed to initialize default network: %w", err) | ||
| } | ||
|
|
||
| url, err = retry.DoWithData(func() (string, error) { | ||
| // Initialize a port for the container | ||
|
|
@@ -162,100 +174,128 @@ func (p *CTFChainProvider) startContainer(chainID string) (string, ton.APIClient | |
|
|
||
| return output.Nodes[0].ExternalHTTPUrl, nil | ||
| }, | ||
| retry.Context(p.t.Context()), | ||
| retry.Context(ctx), | ||
| retry.Attempts(attempts), | ||
| retry.Delay(1*time.Second), | ||
| retry.DelayType(retry.FixedDelay), | ||
| retry.OnRetry(func(attempt uint, err error) { | ||
| p.t.Logf("Attempt %d/%d: Failed to start CTF Ton container: %v", attempt+1, attempts, err) | ||
| }), | ||
| ) | ||
| require.NoError(p.t, err, "Failed to start CTF Ton container after %d attempts", attempts) | ||
| if err != nil { | ||
| return "", nil, fmt.Errorf("failed to start CTF Ton container after %d attempts: %w", attempts, err) | ||
| } | ||
|
|
||
| connectionPool, err := createLiteclientConnectionPool(p.t.Context(), url) | ||
| require.NoError(p.t, err) | ||
| connectionPool, err := createLiteclientConnectionPool(ctx, url) | ||
| if err != nil { | ||
| return "", nil, fmt.Errorf("failed to create liteclient connection pool: %w", err) | ||
| } | ||
|
|
||
| client := ton.NewAPIClient(connectionPool, ton.ProofCheckPolicyFast) | ||
|
|
||
| // check connection, CTFv2 handles the readiness | ||
| mb := getMasterchainBlockID(p.t, client) | ||
| mb, err := getMasterchainBlockID(ctx, client) | ||
| if err != nil { | ||
| return "", nil, fmt.Errorf("failed to get masterchain block ID: %w", err) | ||
| } | ||
|
|
||
| // set starting point to verify master block proofs chain | ||
| client.SetTrustedBlock(mb) | ||
|
|
||
| retryCount := p.getRetryCount() | ||
|
|
||
| return url, client.WithRetry(retryCount) | ||
| return url, client, nil | ||
| } | ||
|
|
||
| // Note: this utility functions can be replaced once we have in the chainlink-ton utils package | ||
| func createTonWallet(t *testing.T, client ton.APIClientWrapped, versionConfig wallet.VersionConfig, option wallet.Option) *wallet.Wallet { | ||
| t.Helper() | ||
|
|
||
| func createTonWallet(client ton.APIClientWrapped, versionConfig wallet.VersionConfig, option wallet.Option) (*wallet.Wallet, error) { | ||
|
||
| seed := wallet.NewSeed() | ||
| rw, err := wallet.FromSeed(client, seed, versionConfig) | ||
| require.NoError(t, err) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to create wallet from seed: %w", err) | ||
| } | ||
| pw, perr := wallet.FromPrivateKeyWithOptions(client, rw.PrivateKey(), versionConfig, option) | ||
| require.NoError(t, perr) | ||
| if perr != nil { | ||
| return nil, fmt.Errorf("failed to create wallet from private key: %w", perr) | ||
| } | ||
|
|
||
| return pw | ||
| return pw, nil | ||
| } | ||
|
|
||
| func fundTonWallets(t *testing.T, client ton.APIClientWrapped, recipients []*address.Address, amounts []tlb.Coins) { | ||
| t.Helper() | ||
| func fundTonWallets(ctx context.Context, client ton.APIClientWrapped, recipients []*address.Address, amounts []tlb.Coins) error { | ||
| if len(amounts) != len(recipients) { | ||
| return errors.New("recipients and amounts must have the same length") | ||
| } | ||
|
|
||
| require.Len(t, amounts, len(recipients), "recipients and amounts must have the same length") | ||
| // initialize the prefunded wallet(Highload-V2), for other wallets, see https://github.com/neodix42/mylocalton-docker#pre-installed-wallets | ||
| version := wallet.HighloadV2Verified //nolint:staticcheck // SA1019: only available option in mylocalton-docker | ||
| rawHlWallet, err := wallet.FromSeed(client, strings.Fields(blockchain.DefaultTonHlWalletMnemonic), version) | ||
| require.NoError(t, err) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create wallet from seed: %w", err) | ||
| } | ||
|
|
||
| mcFunderWallet, err := wallet.FromPrivateKeyWithOptions(client, rawHlWallet.PrivateKey(), version, wallet.WithWorkchain(-1)) | ||
| require.NoError(t, err) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create wallet from private key: %w", err) | ||
| } | ||
|
|
||
| funder, err := mcFunderWallet.GetSubwallet(uint32(42)) | ||
| require.NoError(t, err) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get subwallet: %w", err) | ||
| } | ||
|
|
||
| // double check funder address | ||
| require.Equal(t, blockchain.DefaultTonHlWalletAddress, funder.Address().StringRaw(), "funder address mismatch") | ||
| if funder.Address().StringRaw() != blockchain.DefaultTonHlWalletAddress { | ||
| return fmt.Errorf("funder address mismatch: %s != %s", funder.Address().StringRaw(), blockchain.DefaultTonHlWalletAddress) | ||
| } | ||
|
|
||
| // create transfer messages for each recipient | ||
| messages := make([]*wallet.Message, len(recipients)) | ||
| for i, addr := range recipients { | ||
| transfer, terr := funder.BuildTransfer(addr, amounts[i], false, "") | ||
| require.NoError(t, terr) | ||
| if terr != nil { | ||
| return fmt.Errorf("failed to build transfer: %w", terr) | ||
| } | ||
| messages[i] = transfer | ||
| } | ||
| _, _, txerr := funder.SendManyWaitTransaction(t.Context(), messages) | ||
| require.NoError(t, txerr, "airdrop transaction failed") | ||
|
|
||
| // we don't wait for the transaction to be confirmed here, as it may take some time | ||
| } | ||
| // the name SendManyWaitTransaction is misleading, it doesn't wait for the transaction to be confirmed, | ||
| // it just sends the transactions(TON has asynchronous transactions) | ||
| _, _, txerr := funder.SendManyWaitTransaction(ctx, messages) | ||
| if txerr != nil { | ||
| return fmt.Errorf("failed to send many wait transaction: %w", txerr) | ||
| } | ||
|
|
||
| func getMasterchainBlockID(t *testing.T, client *ton.APIClient) *ton.BlockIDExt { | ||
| t.Helper() | ||
| return nil | ||
| } | ||
|
|
||
| func getMasterchainBlockID(ctx context.Context, client ton.APIClientWrapped) (*ton.BlockIDExt, error) { | ||
| var masterchainBlockID *ton.BlockIDExt | ||
| // check connection, CTFv2 handles the readiness | ||
| err := retry.Do(func() error { | ||
| var err error | ||
| masterchainBlockID, err = client.GetMasterchainInfo(t.Context()) | ||
| masterchainBlockID, err = client.GetMasterchainInfo(ctx) | ||
|
|
||
| return err | ||
| }, | ||
| retry.Context(t.Context()), | ||
| retry.Context(ctx), | ||
| retry.Attempts(30), | ||
| retry.Delay(1*time.Second), | ||
| retry.DelayType(retry.FixedDelay), | ||
| ) | ||
| require.NoError(t, err, "TON network not ready") | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to get masterchain info: %w", err) | ||
| } | ||
|
|
||
| // return masterchain block for setting trusted block | ||
| return masterchainBlockID | ||
| return masterchainBlockID, nil | ||
| } | ||
|
|
||
| // Name returns the name of the CTFChainProvider. | ||
| func (*CTFChainProvider) Name() string { | ||
| return "Ton CTF Chain Provider" | ||
| return "TON CTF Chain Provider" | ||
| } | ||
|
|
||
| // ChainSelector returns the chain selector of the Aptos chain managed by this provider. | ||
| // ChainSelector returns the chain selector of the TON chain managed by this provider. | ||
| func (p *CTFChainProvider) ChainSelector() uint64 { | ||
| return p.selector | ||
| } | ||
|
|
@@ -266,10 +306,6 @@ func (p *CTFChainProvider) BlockChain() chain.BlockChain { | |
| return *p.chain | ||
| } | ||
|
|
||
| func (p *CTFChainProvider) getRetryCount() int { | ||
| return p.config.RetryCount | ||
| } | ||
|
|
||
| // getImage returns the configured Docker image, or the default if not specified. | ||
| func (p *CTFChainProvider) getImage() string { | ||
| if p.config.Image != "" { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.