lightning-terminal/lnd_ready.go
bitromortac c478f8227a terminal: gate wallet-ready status on lnd's actual RPC readiness
readyChan/unlockChan only guarantee lnd's gRPC listener socket is bound,
not that lnd's RPC interceptor has left WAITING_TO_START. Callers that
poll litd's status (itest's WaitForLNDWalletReady, litcli status, the UI)
could observe "Wallet Ready" and then immediately hit a "waiting to
start, RPC services not available" error, which was the root cause of a
flake in TestLightningTerminal/.../terminal_stateless_init_mode (CI run
29286599979, PR #1322). Poll lnd's StateService, which is exempt from
both the macaroon and RPC-readiness checks, until it reports leaving
WAITING_TO_START before advertising the wallet as ready.
2026-07-16 09:53:44 +00:00

54 lines
1.4 KiB
Go

package terminal
import (
"context"
"fmt"
"time"
"github.com/lightningnetwork/lnd/lnrpc"
)
// waitForLndRPCReady polls lnd's StateService until lnd's RPC interceptor has
// left the WAITING_TO_START state, or timeout elapses. Unlike every other
// lnd RPC, the StateService is exempt from both lnd's macaroon check and its
// RPC-readiness check (see lnd's rpcperms.InterceptorChain), so it can be
// queried on a fresh connection before the wallet is unlocked and before any
// macaroon exists. This lets us distinguish lnd's gRPC listener merely being
// bound (which is all readyChan/unlockChan guarantee) from lnd actually being
// able to service non-State RPCs.
func waitForLndRPCReady(ctx context.Context, stateClient lnrpc.StateClient,
timeout time.Duration) error {
timer := time.NewTimer(timeout)
defer timer.Stop()
var lastErr error
for {
resp, err := stateClient.GetState(ctx, &lnrpc.GetStateRequest{})
switch {
case err != nil:
lastErr = err
case resp.State != lnrpc.WalletState_WAITING_TO_START:
return nil
}
select {
case <-time.After(stateServicePollInterval):
case <-timer.C:
if lastErr != nil {
return fmt.Errorf("lnd's RPC interceptor "+
"did not leave WAITING_TO_START "+
"within %v: %w", timeout, lastErr)
}
return fmt.Errorf("lnd's RPC interceptor did not "+
"leave WAITING_TO_START within %v", timeout)
case <-ctx.Done():
return ctx.Err()
}
}
}