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

@@ -95,14 +95,46 @@ func GetCoinsupply(rpcClient *rpcclient.Client) (string, string, int, int, int,
return "", "", -1, -1, -1, -1, errors.Wrap(err, "error reading JSON response")
}
result := f.(map[string]interface{})["result"].(string)
coin := f.(map[string]interface{})["coin"].(string)
height := f.(map[string]interface{})["height"].(float64)
supply := f.(map[string]interface{})["supply"].(float64)
zfunds := f.(map[string]interface{})["zfunds"].(float64)
total := f.(map[string]interface{})["total"].(float64)
coinsupply, ok := f.(map[string]interface{})
if !ok {
return "", "", -1, -1, -1, -1, errors.New("unexpected coinsupply response format")
}
return result, coin, int(height), int(supply), int(zfunds), int(total), nil
getStringField := func(key string) string {
value, ok := coinsupply[key]
if !ok || value == nil {
return ""
}
if strValue, ok := value.(string); ok {
return strValue
}
return fmt.Sprintf("%v", value)
}
getNumberField := func(key string) int {
value, ok := coinsupply[key]
if !ok || value == nil {
return 0
}
number, ok := value.(float64)
if !ok {
return 0
}
return int(number)
}
result := getStringField("result")
coin := getStringField("coin")
height := getNumberField("height")
supply := getNumberField("supply")
zfunds := getNumberField("zfunds")
total := getNumberField("total")
return result, coin, height, supply, zfunds, total, nil
}
func getBlockFromRPC(rpcClient *rpcclient.Client, height int) (*walletrpc.CompactBlock, error) {
@@ -225,6 +257,13 @@ func BlockIngestor(rpcClient *rpcclient.Client, cache *BlockCache, log *logrus.E
}
func GetBlock(rpcClient *rpcclient.Client, cache *BlockCache, height int) (*walletrpc.CompactBlock, error) {
// Don't serve blocks above the advertised (lag-adjusted) tip, so wallets can
// neither sync nor anchor shielded spends into the unstable reorg zone.
if !cache.HeightAllowed(height) {
return nil, errors.New(
fmt.Sprintf("Block %d is above the advertised tip (reorg-lag protection)", height))
}
// First, check the cache to see if we have the block
block := cache.Get(height)
if block != nil {