feat(frontend): implement GetMempoolStream for 0-conf lightwallet txs
The CompactTxStreamer service did not implement any mempool RPC, so lightwallets (SilentDragonXLite / ObsidianDragonLite) received UNIMPLEMENTED from get_mempool_stream and permanently stopped their mempool monitor. As a result the lite wallet could not see 0-confirmation transactions and only surfaced incoming shielded chat messages after they were mined (~1 block). Add GetMempoolStream(Empty) returns (stream RawTransaction). A single process-wide monitor (frontend/mempool.go) polls the node's getrawmempool and fetches each new tx via getrawtransaction (full serialized bytes, so shielded memos are preserved -- CompactTx's 52-byte prefix would not carry a memo), then fans the tx out to every subscribed stream. This avoids polling the node once per connected wallet: N clients share one poll loop. The monitor starts lazily on the first subscription, idles without touching the node when no clients are connected, and closes all subscriber streams when a new block is mined (the semantics the client's monitor loop expects; the client re-syncs and reconnects). Purely additive and wire-compatible: RawTransaction field numbers and the cash.z.wallet.sdk.rpc package match the deployed client; existing clients that never call GetMempoolStream are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -116,6 +116,45 @@ func (s *SqlStreamer) GetAddressTxids(addressBlockFilter *walletrpc.TransparentA
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMempoolStream streams currently-unconfirmed (mempool) transactions to the client as full
|
||||
// RawTransactions, so lightwallets can detect 0-confirmation transactions -- including reading
|
||||
// shielded memos (e.g. incoming chat messages) -- without waiting for a block. Each mempool tx is
|
||||
// emitted once; the stream stays open until a new block is mined, at which point it returns
|
||||
// (closing the stream) so the client re-syncs the block and reconnects. This mirrors the
|
||||
// zecwallet/Hush lightwalletd semantics the wallets already expect.
|
||||
//
|
||||
// The node's mempool is polled by a single process-wide monitor that fans out to all subscribers
|
||||
// (see mempool.go), so the node isn't polled once per connected wallet.
|
||||
func (s *SqlStreamer) GetMempoolStream(_ *walletrpc.Empty, resp walletrpc.CompactTxStreamer_GetMempoolStreamServer) error {
|
||||
monitor := getMempoolMonitor(s.client, s.cache, s.log)
|
||||
id, ch, snapshot := monitor.subscribe()
|
||||
defer monitor.unsubscribe(id)
|
||||
|
||||
// First send the txs already in this block's mempool, so a wallet that connects mid-block still
|
||||
// sees them.
|
||||
for _, rtx := range snapshot {
|
||||
if err := resp.Send(rtx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Then stream subsequent txs until the monitor closes the channel (a new block was mined) or
|
||||
// the client disconnects.
|
||||
for {
|
||||
select {
|
||||
case <-resp.Context().Done():
|
||||
return nil
|
||||
case rtx, ok := <-ch:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if err := resp.Send(rtx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SqlStreamer) GetBlock(ctx context.Context, id *walletrpc.BlockID) (*walletrpc.CompactBlock, error) {
|
||||
if id.Height == 0 && id.Hash == nil {
|
||||
return nil, ErrUnspecified
|
||||
|
||||
Reference in New Issue
Block a user