Summary
ConnPool.BestMasterchainInfoClient() wraps p.bestConnection() without checking it for nil. When
the pool has no healthy connection, bestConnection() returns nil and the returned
*MasterchainInfoClient panics on first use with a nil pointer dereference, rather than surfacing
ErrNoConnections.
Present in v1.17.0 and still on main as of v1.23.0.
The code
liteapi/pool/conn_pool.go#L264-L269 and #L255-L262 (v1.17.0):
func (p *ConnPool) BestMasterchainInfoClient() *MasterchainInfoClient {
return &MasterchainInfoClient{
conn: p.bestConnection(), // nil when no connection is healthy
}
}
func (s *MasterchainInfoClient) LiteServerGetMasterchainInfo(ctx context.Context) (liteclient.LiteServerMasterchainInfoC, error) {
info, err := s.conn.Client().LiteServerGetMasterchainInfo(ctx) // panics: s.conn is nil
...
}
The sibling function immediately below — #L271-L276 — already handles exactly this case, which is what makes the omission look unintentional:
func (p *ConnPool) BestMasterchainClient(ctx context.Context) (*liteclient.Client, ton.BlockIDExt, error) {
bestConnection := p.bestConnection()
if bestConnection == nil {
return nil, ton.BlockIDExt{}, ErrNoConnections
}
...
}
LiteServerGetMasterchainInfoExt on the same type has the same problem, and ErrNoConnections already exists.
Still present on main today: BestMasterchainInfoClient#L295-L299, LiteServerGetMasterchainInfo#L283, guarded sibling at #L302.
Reproduction
Construct a client against a liteserver endpoint that is not accepting connections (for example, a
local node that has not yet bound its port), then call GetMasterchainInfo:
panic: runtime error: invalid memory address or nil pointer dereference
github.com/tonkeeper/tongo/liteapi/pool.(*MasterchainInfoClient).LiteServerGetMasterchainInfo
liteapi/pool/conn_pool.go:256
github.com/tonkeeper/tongo/liteapi.(*Client).GetMasterchainInfo
liteapi/client.go:324
Why
We hit this in production. A service using tongo started moments before its local liteserver was
accepting connections, so the pool was built empty. Because the pool is constructed once and the
nil is never re-checked, every subsequent request panicked for the lifetime of the process —
the condition never cleared, even long after the liteserver became available.
Inside an HTTP handler this is particularly hard to diagnose: net/http recovers the panic per
connection, so the process stays up and its health endpoint keeps returning 200, while every real
request dies with EOF / "empty reply from server" and no HTTP status. From the outside it looks
like a network fault rather than an application bug.
An ErrNoConnections return would have been retried and recovered automatically.
Suggested fix
Mirror BestMasterchainClient — return the error rather than a client that cannot be used:
func (p *ConnPool) BestMasterchainInfoClient() (*MasterchainInfoClient, error) {
bestConnection := p.bestConnection()
if bestConnection == nil {
return nil, ErrNoConnections
}
return &MasterchainInfoClient{conn: bestConnection}, nil
}
If changing the signature is undesirable for compatibility, a nil check inside
LiteServerGetMasterchainInfo / LiteServerGetMasterchainInfoExt returning ErrNoConnections
would fix the panic without an API break.
Happy to send a PR for either shape.
Summary
ConnPool.BestMasterchainInfoClient()wrapsp.bestConnection()without checking it for nil. Whenthe pool has no healthy connection,
bestConnection()returns nil and the returned*MasterchainInfoClientpanics on first use with a nil pointer dereference, rather than surfacingErrNoConnections.Present in v1.17.0 and still on
mainas of v1.23.0.The code
liteapi/pool/conn_pool.go#L264-L269and#L255-L262(v1.17.0):The sibling function immediately below —
#L271-L276— already handles exactly this case, which is what makes the omission look unintentional:LiteServerGetMasterchainInfoExton the same type has the same problem, andErrNoConnectionsalready exists.Still present on
maintoday:BestMasterchainInfoClient#L295-L299,LiteServerGetMasterchainInfo#L283, guarded sibling at#L302.Reproduction
Construct a client against a liteserver endpoint that is not accepting connections (for example, a
local node that has not yet bound its port), then call
GetMasterchainInfo:Why
We hit this in production. A service using tongo started moments before its local liteserver was
accepting connections, so the pool was built empty. Because the pool is constructed once and the
nil is never re-checked, every subsequent request panicked for the lifetime of the process —
the condition never cleared, even long after the liteserver became available.
Inside an HTTP handler this is particularly hard to diagnose:
net/httprecovers the panic perconnection, so the process stays up and its health endpoint keeps returning 200, while every real
request dies with
EOF/ "empty reply from server" and no HTTP status. From the outside it lookslike a network fault rather than an application bug.
An
ErrNoConnectionsreturn would have been retried and recovered automatically.Suggested fix
Mirror
BestMasterchainClient— return the error rather than a client that cannot be used:If changing the signature is undesirable for compatibility, a nil check inside
LiteServerGetMasterchainInfo/LiteServerGetMasterchainInfoExtreturningErrNoConnectionswould fix the panic without an API break.
Happy to send a PR for either shape.