From f9c452d5e3509b76bd5f1083bfded62b7b1fa5ff Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Wed, 27 May 2026 18:43:36 -0300 Subject: [PATCH 1/4] fix: client didn't get rotated when creating a new pool --- pkg/relay/chain.go | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/pkg/relay/chain.go b/pkg/relay/chain.go index f9cb3a1e3..5fa77ee59 100644 --- a/pkg/relay/chain.go +++ b/pkg/relay/chain.go @@ -87,7 +87,7 @@ type chain struct { clientCache map[int]*cachedClient cacheMu sync.RWMutex - pool *liteclient.ConnectionPool + pools map[int]*liteclient.ConnectionPool } func NewChain(cfg *config.TOMLConfig, opts ChainOpts) (Chain, error) { @@ -112,6 +112,7 @@ func newChain(cfg *config.TOMLConfig, loopKs loop.Keystore, lggr logger.Logger, lggr: logger.Named(lggr, "Chain"), ds: ds, clientCache: make(map[int]*cachedClient), + pools: make(map[int]*liteclient.ConnectionPool), } // TODO(@jadepark-dev): TXM technically doesn't need SignedAPIClient, revisit to refactor @@ -214,10 +215,12 @@ func (c *chain) Close() error { err := services.CloseAll(c.txm, c.lp, c.bm) c.cacheMu.Lock() - if c.pool != nil { - c.pool.Stop() - c.pool = nil + for _, pool := range c.pools { + if pool != nil { + pool.Stop() + } } + c.pools = make(map[int]*liteclient.ConnectionPool) c.cacheMu.Unlock() return err @@ -454,9 +457,9 @@ func (c *chain) GetSignerWallet(ctx context.Context, client ton.APIClientWrapped // 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() + c.cacheMu.Lock() + defer c.cacheMu.Unlock() + pool := c.pools[nodeIndex] if pool != nil { return pool, nil } @@ -467,15 +470,7 @@ func (c *chain) getOrCreatePool(ctx context.Context, nodeIndex int) (*liteclient 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() + c.pools[nodeIndex] = pool return pool, nil } From cd8bacd1a6eb2261000e6780db54a9900474776c Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Thu, 28 May 2026 12:34:26 -0300 Subject: [PATCH 2/4] ref: split lock --- pkg/relay/chain.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/pkg/relay/chain.go b/pkg/relay/chain.go index 5fa77ee59..a1cac801c 100644 --- a/pkg/relay/chain.go +++ b/pkg/relay/chain.go @@ -457,11 +457,13 @@ func (c *chain) GetSignerWallet(ctx context.Context, client ton.APIClientWrapped // 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.Lock() - defer c.cacheMu.Unlock() - pool := c.pools[nodeIndex] - if pool != nil { - return pool, nil + { // restrict scope of read lock + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + cachedPool := c.pools[nodeIndex] + if cachedPool != nil { + return cachedPool, nil + } } liteServerURL := c.cfg.Nodes[nodeIndex].URL.String() @@ -470,6 +472,15 @@ func (c *chain) getOrCreatePool(ctx context.Context, nodeIndex int) (*liteclient return nil, err } + c.cacheMu.Lock() + defer c.cacheMu.Unlock() + + // Double-check: another goroutine may have created it + cachedPool := c.pools[nodeIndex] + if cachedPool != nil { + return cachedPool, nil + } + c.pools[nodeIndex] = pool return pool, nil From 2f0752c362cf3f6a10b7d4a84b8319c69986b346 Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Thu, 28 May 2026 14:11:42 -0300 Subject: [PATCH 3/4] fix: defer gets called at the end of the function, not the scope --- pkg/relay/chain.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pkg/relay/chain.go b/pkg/relay/chain.go index a1cac801c..81bd113a1 100644 --- a/pkg/relay/chain.go +++ b/pkg/relay/chain.go @@ -457,13 +457,11 @@ func (c *chain) GetSignerWallet(ctx context.Context, client ton.APIClientWrapped // 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) { - { // restrict scope of read lock - c.cacheMu.RLock() - defer c.cacheMu.RUnlock() - cachedPool := c.pools[nodeIndex] - if cachedPool != nil { - return cachedPool, nil - } + c.cacheMu.RLock() + cachedPool := c.pools[nodeIndex] + c.cacheMu.RUnlock() + if cachedPool != nil { + return cachedPool, nil } liteServerURL := c.cfg.Nodes[nodeIndex].URL.String() @@ -476,7 +474,7 @@ func (c *chain) getOrCreatePool(ctx context.Context, nodeIndex int) (*liteclient defer c.cacheMu.Unlock() // Double-check: another goroutine may have created it - cachedPool := c.pools[nodeIndex] + cachedPool = c.pools[nodeIndex] if cachedPool != nil { return cachedPool, nil } From 5f359154efe88fc99070e069203524d0fbfa7fc0 Mon Sep 17 00:00:00 2001 From: Patricio Tourne Passarino Date: Thu, 28 May 2026 15:38:16 -0300 Subject: [PATCH 4/4] fix: close discarded pool --- pkg/relay/chain.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/relay/chain.go b/pkg/relay/chain.go index 81bd113a1..06a27491b 100644 --- a/pkg/relay/chain.go +++ b/pkg/relay/chain.go @@ -215,13 +215,14 @@ func (c *chain) Close() error { err := services.CloseAll(c.txm, c.lp, c.bm) c.cacheMu.Lock() - for _, pool := range c.pools { + poolsToBeClosed := c.pools + c.pools = make(map[int]*liteclient.ConnectionPool) + c.cacheMu.Unlock() + for _, pool := range poolsToBeClosed { if pool != nil { pool.Stop() } } - c.pools = make(map[int]*liteclient.ConnectionPool) - c.cacheMu.Unlock() return err }) @@ -471,13 +472,15 @@ func (c *chain) getOrCreatePool(ctx context.Context, nodeIndex int) (*liteclient } c.cacheMu.Lock() - defer c.cacheMu.Unlock() // Double-check: another goroutine may have created it cachedPool = c.pools[nodeIndex] if cachedPool != nil { + c.cacheMu.Unlock() + pool.Stop() // discard the one we just made return cachedPool, nil } + defer c.cacheMu.Unlock() c.pools[nodeIndex] = pool