diff --git a/devenv/offchain.go b/devenv/offchain.go index 15c986696..66040e279 100644 --- a/devenv/offchain.go +++ b/devenv/offchain.go @@ -92,6 +92,9 @@ func (m *CCIP16TON) ConfigureNodes(ctx context.Context, bc *blockchain.Input) (s Enabled = true NetworkName = 'ton-localnet' + [TON.TransactionManager] + CleanupInterval = '5m' + [[TON.Nodes]] Name = '%s' URL = '%s'`, @@ -105,9 +108,11 @@ func (m *CCIP16TON) ConfigureNodes(ctx context.Context, bc *blockchain.Input) (s ), nil } +const defaultNodeFundingTON = "500" + func (m *CCIP16TON) FundNodes(ctx context.Context, cls []*simple_node_set.Input, nodeKeyBundles map[string]clclient.NodeKeysBundle, bc *blockchain.Input, linkAmount, nativeAmount *big.Int) error { l := zerolog.Ctx(ctx) - l.Info().Msg("Funding CL nodes with native and LINK") + l.Info().Str("amount", defaultNodeFundingTON).Msg("Funding CL nodes with native TON") keys := make([]*address.Address, 0) amounts := make([]tlb.Coins, 0) for _, nk := range nodeKeyBundles { @@ -116,7 +121,7 @@ func (m *CCIP16TON) FundNodes(ctx context.Context, cls []*simple_node_set.Input, return err } keys = append(keys, address.MustParseAddr(addr)) - amounts = append(amounts, tlb.MustFromTON(nativeAmount.String())) + amounts = append(amounts, tlb.MustFromTON(defaultNodeFundingTON)) } client, err := testutils.CreateClient(ctx, bc.Out.Nodes[0].ExternalHTTPUrl) if err != nil { diff --git a/pkg/relay/chain.go b/pkg/relay/chain.go index f9e68a5d5..62b43144a 100644 --- a/pkg/relay/chain.go +++ b/pkg/relay/chain.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "github.com/xssnick/tonutils-go/liteclient" "github.com/xssnick/tonutils-go/ton" "github.com/xssnick/tonutils-go/ton/wallet" "github.com/xssnick/tonutils-go/tvm/cell" @@ -85,6 +86,7 @@ type chain struct { clientCache map[int]*cachedClient cacheMu sync.RWMutex + pool *liteclient.ConnectionPool } func NewChain(cfg *config.TOMLConfig, opts ChainOpts) (Chain, error) { @@ -208,7 +210,16 @@ func (c *chain) Start(ctx context.Context) error { func (c *chain) Close() error { return c.starter.StopOnce("Chain", func() error { c.lggr.Debug("Stopping txm, log poller, and balance monitor") - return services.CloseAll(c.txm, c.lp, c.bm) + err := services.CloseAll(c.txm, c.lp, c.bm) + + c.cacheMu.Lock() + if c.pool != nil { + c.pool.Stop() + c.pool = nil + } + c.cacheMu.Unlock() + + return err }) } @@ -355,8 +366,7 @@ func (c *chain) GetClient(ctx context.Context) (ton.APIClientWrapped, error) { } // Build new client, expected URL format: liteserver://publickey@host:port - liteServerURL := node.URL.String() - connectionPool, cerr := tonchain.CreateLiteserverConnectionPool(ctx, liteServerURL) + connectionPool, cerr := c.getOrCreatePool(ctx, i) if cerr != nil { c.lggr.Warnw("failed to get connection pool", "name", node.Name, "ton-url", node.URL, "err", cerr) continue @@ -440,6 +450,35 @@ func (c *chain) GetSignerWallet(ctx context.Context, client ton.APIClientWrapped return w, nil } +// getOrCreatePool returns the long-lived ConnectionPool, creating it on first call. +// Caller must NOT hold cacheMu — this method locks it internally. +func (c *chain) getOrCreatePool(ctx context.Context, nodeIndex int) (*liteclient.ConnectionPool, error) { + c.cacheMu.RLock() + pool := c.pool + c.cacheMu.RUnlock() + if pool != nil { + return pool, nil + } + + liteServerURL := c.cfg.Nodes[nodeIndex].URL.String() + pool, err := tonchain.CreateLiteserverConnectionPool(ctx, liteServerURL) + if err != nil { + return nil, err + } + + c.cacheMu.Lock() + // Double-check: another goroutine may have created it + if c.pool != nil { + c.cacheMu.Unlock() + pool.Stop() // discard the one we just made + return c.pool, nil + } + c.pool = pool + c.cacheMu.Unlock() + + return pool, nil +} + func (c *chain) evictClient(index int, name string, reason string) { c.cacheMu.Lock() defer c.cacheMu.Unlock() diff --git a/pkg/ton/tracetracking/message.go b/pkg/ton/tracetracking/message.go index 00beae1c1..83c8b77bb 100644 --- a/pkg/ton/tracetracking/message.go +++ b/pkg/ton/tracetracking/message.go @@ -348,8 +348,14 @@ func (m *ReceivedMessage) WaitForOutgoingMessagesToBeReceived(ctx context.Contex receivedMessage, err := waitForMatchingMessage(subCtx, transactionsReceived, sentMessage) - // Cancel context as soon as we have the result to prevent leaks + // Cancel context and drain the channel so the SubscribeOnTransactions goroutine + // can unblock from its bare `channel <- tx` send, reach the `workerCtx.Done()` + // select, and exit. Without draining, the goroutine leaks forever. cancel() + go func() { + for range transactionsReceived { + } + }() if err != nil { return err diff --git a/pkg/txm/txstore.go b/pkg/txm/txstore.go index 3e049f5c3..12d1b8c06 100644 --- a/pkg/txm/txstore.go +++ b/pkg/txm/txstore.go @@ -2,6 +2,7 @@ package txm import ( "fmt" + "math/big" "sort" "sync" @@ -20,7 +21,7 @@ type UnconfirmedTx struct { } type FinalizedTx struct { - ReceivedMessage tracetracking.ReceivedMessage + TotalActionFees *big.Int ExitCode tvm.ExitCode TraceSucceeded bool } @@ -78,7 +79,7 @@ func (s *TxStore) MarkFinalized(lt uint64, success bool, exitCode tvm.ExitCode) // move transaction to finalized map s.finalizedTxs[lt] = &FinalizedTx{ - ReceivedMessage: unconfirmedTx.Tx.ReceivedMessage, + TotalActionFees: unconfirmedTx.Tx.ReceivedMessage.TotalActionFees, ExitCode: exitCode, TraceSucceeded: success, } @@ -125,7 +126,7 @@ func (s *TxStore) GetTxState(lt uint64) (tracetracking.MsgStatus, bool, tvm.Exit if tx, exists := s.finalizedTxs[lt]; exists { // Transaction is finalized (success or failure is indicated separately) - totalActionFees := tlb.MustFromNano(tx.ReceivedMessage.TotalActionFees, 9) + totalActionFees := tlb.MustFromNano(tx.TotalActionFees, 9) return tracetracking.Finalized, tx.TraceSucceeded, tx.ExitCode, totalActionFees, true } diff --git a/scripts/.core_version b/scripts/.core_version index 2b3d45999..8e9a8f709 100644 --- a/scripts/.core_version +++ b/scripts/.core_version @@ -1 +1 @@ -306cebe992240ebd08cec58adef6a2c3308b91d1 +b7531451e5f0569f5f617982a09a61454eddfe0c diff --git a/scripts/e2e/run-test.sh b/scripts/e2e/run-test.sh index aaa70643f..72889e59a 100755 --- a/scripts/e2e/run-test.sh +++ b/scripts/e2e/run-test.sh @@ -84,7 +84,8 @@ setup_contracts "$CHAINLINK_CORE_DIR" # verifying gitRef match might be redundant for local workflow. verify_plugin_config "$CHAINLINK_CORE_DIR" -build_ton_binary +# disable LOOP plugin — run TON relayer in-process for profiling +export CL_TON_CMD="" # test database URL availability validation if [ -z "${CL_DATABASE_URL:-}" ]; then @@ -110,7 +111,7 @@ fi log_info "=== CCIP Test Execution ===" log_info "Using Chainlink Core: $CHAINLINK_CORE_DIR" log_info "Using Database URL: $CL_DATABASE_URL" -log_info "Using TON Binary: $CL_TON_CMD" +log_info "CL_TON_CMD: '${CL_TON_CMD}' (empty = embedded/in-process)" log_info "Test Command: $ARG_TEST_COMMAND" log_info "Executing Test Command in $CHAINLINK_CORE_DIR: $ARG_TEST_COMMAND" diff --git a/scripts/e2e/setup-env.sh b/scripts/e2e/setup-env.sh index de4935486..8a3d418a8 100755 --- a/scripts/e2e/setup-env.sh +++ b/scripts/e2e/setup-env.sh @@ -112,11 +112,11 @@ replace_ton_modules() { go mod tidy fi done - + # scan for go.mod files that use chainlink-ton find "$CHAINLINK_CORE_DIR" -name "go.mod" -type f -print0 | while IFS= read -r -d '' gomod; do dir=$(dirname "$gomod") - + # check if any chainlink-ton modules are used needs_update=false for mod in "${!MODULES_TON[@]}"; do @@ -125,10 +125,10 @@ replace_ton_modules() { break fi done - + if [ "$needs_update" = true ]; then log_info " Updating ${dir#$CHAINLINK_CORE_DIR/}" - + pushd "$dir" > /dev/null for mod in "${!MODULES_TON[@]}"; do if grep -q "$mod" go.mod; then @@ -140,7 +140,7 @@ replace_ton_modules() { popd > /dev/null fi done - + go run github.com/jmank88/gomods@v0.1.6 tidy log_info "Module replacements complete" } @@ -188,9 +188,8 @@ validate_core_version "$CHAINLINK_CORE_DIR" # This allows testing with previous contract versions without rebuilding setup_contracts "$CHAINLINK_CORE_DIR" -# TODO: Revisit to check if this is needed. we already have nix build for chainlink-ton. -# but the point is building the binary for every test run in local dev env -build_ton_binary +# disable LOOP plugin — run TON relayer in-process for profiling +export CL_TON_CMD="" setup_postgres