lightwalletd: adaptive reorg-lag tip + non-blocking startup coinsupply

Adaptive-lag: advertise a tip that trails the real tip by a reorg-rate-driven
lag (GetLatestBlock/GetLightdInfo) so wallets anchor shielded spends at a
settled height during reorg churn. Configurable via
-adaptive-lag/-lag-min/-lag-max/-lag-window; monitor_lwd.sh runs
-lag-min 4 -lag-max 12 -lag-window 30.

Startup coinsupply: the informational startup coinsupply RPC (result used only
for a log line) blocked the gRPC bind for minutes on a node whose supply index
is cold, keeping the lite endpoint down on every restart while it also starved
the block-cache ingestor. Run it in a goroutine so the bind is never blocked;
clients still get coinsupply on demand via the GetCoinsupply RPC.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 01:05:10 -05:00
parent b1b0d4559b
commit 2bab58c6d2
5 changed files with 192 additions and 23 deletions

View File

@@ -85,6 +85,11 @@ type Options struct {
logPath string `json:"log_file,omitempty"`
confPath string `json:"conf_file,omitempty"`
cacheSize int `json:"cache_size,omitempty"`
adaptiveLag bool `json:"adaptive_lag,omitempty"`
lagMin int `json:"lag_min,omitempty"`
lagMax int `json:"lag_max,omitempty"`
lagWindowMin int `json:"lag_window_min,omitempty"`
}
func main() {
@@ -99,6 +104,10 @@ func main() {
flag.StringVar(&opts.logPath, "log-file", "", "log file to write to")
flag.StringVar(&opts.confPath, "conf-file", "", "conf file to pull RPC creds from")
flag.IntVar(&opts.cacheSize, "cache-size", 400000, "number of blocks to hold in the cache")
flag.BoolVar(&opts.adaptiveLag, "adaptive-lag", true, "advertise a tip that trails the real tip by a reorg-rate-driven lag, so wallets anchor shielded spends at a settled height")
flag.IntVar(&opts.lagMin, "lag-min", 1, "minimum confirmation lag in blocks (applied when the chain is stable)")
flag.IntVar(&opts.lagMax, "lag-max", 12, "maximum confirmation lag in blocks (cap during heavy reorgs)")
flag.IntVar(&opts.lagWindowMin, "lag-window", 30, "minutes of recent reorg history used to size the adaptive lag")
// creating --version as a requirement of help2man
if len(os.Args) > 1 && (os.Args[1] == "--version" || os.Args[1] == "-v") {
@@ -188,18 +197,25 @@ func main() {
log.Info("Got sapling height ", saplingHeight, " chain ", chainName, " branchID ", branchID, " difficulty ", difficulty, longestchain, " longestchain ", notarized, " notarized ")
// Get the Coinsupply from the RPC
result, coin, height, supply, zfunds, total, err := common.GetCoinsupply(rpcClient)
if err != nil {
log.WithFields(logrus.Fields{
"error": err,
}).Warn("Unable to get coinsupply")
}
log.Info(" result ", result, " coin ", coin, " height", height, "supply", supply, "zfunds", zfunds, "total", total)
// Fetch coinsupply for an informational startup log line only (the result is not
// used elsewhere). On a node whose supply index is cold this RPC can take minutes,
// so run it in the background rather than blocking the gRPC bind on it — otherwise
// the lite endpoint stays down for the entire duration on every restart. Clients
// still get coinsupply on demand via the GetCoinsupply RPC.
go func() {
result, coin, height, supply, zfunds, total, err := common.GetCoinsupply(rpcClient)
if err != nil {
log.WithFields(logrus.Fields{
"error": err,
}).Warn("Unable to get coinsupply")
return
}
log.Info(" result ", result, " coin ", coin, " height", height, "supply", supply, "zfunds", zfunds, "total", total)
}()
// Initialize the cache
cache := common.NewBlockCache(opts.cacheSize)
cache.ConfigureLag(opts.adaptiveLag, opts.lagWindowMin, opts.lagMin, opts.lagMax)
stopChan := make(chan bool, 1)