Compare commits
19 Commits
0f20b1965c
...
feat/adapt
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bab58c6d2 | |||
| b1b0d4559b | |||
| ec1c479156 | |||
| aae94a4f3c | |||
| 3355c1ea85 | |||
| ebc38c9369 | |||
| f4ef38c42f | |||
|
|
daab197a01 | ||
|
|
ef9b241cfd | ||
|
|
5bab463245 | ||
|
|
24640b178e | ||
|
|
5b5533c890 | ||
|
|
c9f6e7d7f3 | ||
|
|
6cc055b07f | ||
|
|
8937561710 | ||
|
|
0735c58b4c | ||
|
|
1217bade73 | ||
|
|
8881b6f1d6 | ||
|
|
3ef370621c |
32
.gitignore
vendored
32
.gitignore
vendored
@@ -1,5 +1,33 @@
|
||||
# Compiled binaries
|
||||
main
|
||||
grpcfrontend
|
||||
cert.pem
|
||||
key.pem
|
||||
lightwalletd
|
||||
|
||||
# TLS certificates and keys
|
||||
*.pem
|
||||
|
||||
# Database files
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Go test binaries and output
|
||||
*.test
|
||||
*.out
|
||||
*.prof
|
||||
|
||||
# IDE and editor files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Temporary files
|
||||
/tmp/
|
||||
|
||||
# Build output
|
||||
/dist/
|
||||
/build/
|
||||
|
||||
4
AUTHORS
Normal file
4
AUTHORS
Normal file
@@ -0,0 +1,4 @@
|
||||
# The Hush Developers
|
||||
|
||||
Jahway603 https://git.hush.is/jahway603 https://github.com/jahway603
|
||||
Duke Leto https://git.hush.is/duke
|
||||
9
Makefile
9
Makefile
@@ -1,7 +1,8 @@
|
||||
# Copyright (c) 2021 Jahway603 & The Hush Developers
|
||||
# Copyright (c) 2024-2026 The DragonX Developers
|
||||
# Released under the GPLv3
|
||||
#
|
||||
# Hush Lightwalletd Makefile
|
||||
# DragonX Lightwalletd Makefile
|
||||
# author: jahway603
|
||||
#
|
||||
PROJECT_NAME := "lightwalletd"
|
||||
@@ -19,9 +20,9 @@ build-arm:
|
||||
# Build binary for ARM architecture (aarch64)
|
||||
./util/build_arm.sh
|
||||
|
||||
# Stop the hushd process in the hushdlwd container
|
||||
#docker_img_stop_hushd:
|
||||
# docker exec -i hushdlwd hush-cli stop
|
||||
# Stop the dragonxd process in the container
|
||||
#docker_img_stop_dragonxd:
|
||||
# docker exec -i dragonxdlwd dragonx-cli stop
|
||||
|
||||
# Remove and delete ALL images and containers in Docker; assumes containers are stopped
|
||||
#docker_remove_all:
|
||||
|
||||
62
README.md
62
README.md
@@ -1,21 +1,20 @@
|
||||
# Overview
|
||||
|
||||
Hush Lightwalletd is a fork of [lightwalletd](https://github.com/adityapk00/lightwalletd) original from Zcash (ZEC).
|
||||
DragonX Lightwalletd is a fork of [Hush lightwalletd](https://git.hush.is/hush/lightwalletd) which is itself a fork of [lightwalletd](https://github.com/adityapk00/lightwalletd) originally from Zcash (ZEC).
|
||||
|
||||
It is a backend service that provides a bandwidth-efficient interface to the Hush blockchain for [SilentDragonLite cli](https://git.hush.is/hush/silentdragonlite-light-cli) and [SilentDragonLite](https://git.hush.is/hush/SilentDragonLite).
|
||||
It is a backend service that provides a bandwidth-efficient interface to the DragonX blockchain for light wallet clients.
|
||||
|
||||
## Changes from upstream lightwalletd
|
||||
This version of lightwalletd extends lightwalletd and:
|
||||
## Features
|
||||
|
||||
* Adds support for HUSH
|
||||
* Adds support for transparent addresses
|
||||
* Adds several new RPC calls for lightclients
|
||||
* Support for DragonX (standalone chain with `dragonxd`)
|
||||
* Support for transparent addresses
|
||||
* Several RPC calls for light clients
|
||||
* Lots of perf improvements
|
||||
* Replaces SQLite with in-memory cache for Compact Blocks
|
||||
* Replace local Txstore, delegating Tx lookups to hushd
|
||||
* Remove the need for a separate ingestor
|
||||
* In-memory cache for Compact Blocks (replaces SQLite)
|
||||
* Tx lookups delegated to dragonxd
|
||||
* No separate ingestor needed
|
||||
|
||||
## Running your own SDL lightwalletd
|
||||
## Running your own DragonX lightwalletd
|
||||
|
||||
#### 0. First, install Go
|
||||
You will need Go >= 1.13 which you can download from the official [download page](https://golang.org/dl/) or install via your OS package manager.
|
||||
@@ -28,15 +27,15 @@ If you're using Ubuntu or Debian, try:
|
||||
$ sudo apt install golang
|
||||
```
|
||||
|
||||
#### 1. Run a Hush node.
|
||||
Either compile or build the [Hush Daemon (hushd)](https://git.hush.is/hush/hush3).
|
||||
#### 1. Run a DragonX node.
|
||||
Install the DragonX daemon (`dragonxd`) and CLI (`dragonx-cli`).
|
||||
|
||||
Next, change your HUSH3.conf file to something like the following:
|
||||
Next, ensure your DRAGONX.conf file (at `~/.hush/DRAGONX/DRAGONX.conf`) has something like the following:
|
||||
|
||||
```
|
||||
rpcuser=user-CHANGETHIS
|
||||
rpcpassword=pass-CHANGETHIS
|
||||
rpcport=18031
|
||||
rpcport=21769
|
||||
server=1
|
||||
txindex=1
|
||||
rpcworkqueue=256
|
||||
@@ -44,7 +43,7 @@ rpcallowip=127.0.0.1
|
||||
rpcbind=127.0.0.1
|
||||
```
|
||||
|
||||
Then start `hushd` in your command window. You might need to run with `-reindex` the first time if you are enabling the `txindex` or `insightexplorer` options for the first time. The reindex might take a while.
|
||||
Then start `dragonxd`. You might need to run with `-reindex` the first time if you are enabling the `txindex` option for the first time. The reindex might take a while.
|
||||
|
||||
#### 2. Compile lightwalletd
|
||||
Run the build script.
|
||||
@@ -77,8 +76,8 @@ server {
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
location / {
|
||||
# Replace localhost:9067 with the address and port of your gRPC server if using a custom port
|
||||
grpc_pass grpc://your_host.net:9067;
|
||||
# Replace localhost:9069 with the address and port of your gRPC server if using a custom port
|
||||
grpc_pass grpc://your_host.net:9069;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -86,28 +85,18 @@ server {
|
||||
Then run the lightwalletd frontend with the following (Note: we use the "-no-tls" option as we are using NGINX as a reverse proxy and letting it handle the TLS authentication for us instead):
|
||||
|
||||
```
|
||||
./lightwalletd -bind-addr your_host.net:9067 -conf-file ~/.komodo/HUSH3/HUSH3.conf -no-tls
|
||||
./lightwalletd -bind-addr your_host.net:9069 -conf-file ~/.hush/DRAGONX/DRAGONX.conf -no-tls
|
||||
```
|
||||
|
||||
##### Option B: "Let's Encrypt" certificate just using lightwalletd without NGINX
|
||||
The other option is to configure lightwalletd to handle its own TLS authentication. Once you have a certificate that you want to use (from a certificate authority), pass the certificate to the frontend as follows:
|
||||
|
||||
```
|
||||
./lightwalletd -bind-addr 127.0.0.1:9067 -conf-file ~/.komodo/HUSH3/HUSH3.conf -tls-cert /etc/letsencrypt/live/YOURWEBSITE/fullchain.pem -tls-key /etc/letsencrypt/live/YOURWEBSITE/privkey.pem
|
||||
./lightwalletd -bind-addr 127.0.0.1:9069 -conf-file ~/.hush/DRAGONX/DRAGONX.conf -tls-cert /etc/letsencrypt/live/YOURWEBSITE/fullchain.pem -tls-key /etc/letsencrypt/live/YOURWEBSITE/privkey.pem
|
||||
```
|
||||
|
||||
#### 4. Point the `silentdragonlite-cli` to this server
|
||||
You should start seeing the frontend ingest and cache the Hush blocks after ~15 seconds.
|
||||
|
||||
Now, connect to your server! (Substitute with your own below)
|
||||
```
|
||||
git clone https://git.hush.is/hush/silentdragonlite-cli
|
||||
cd silentdragonlite-cli
|
||||
cargo build --release
|
||||
./target/release/silentdragonlite-cli --server https://lite.example.org
|
||||
```
|
||||
|
||||
* If you have trouble compiling silentdragonlite-cli, then [please refer to it's separate documentation here](https://git.hush.is/hush/silentdragonlite-cli) on how to build it and what pre-requisites need to be installed.
|
||||
#### 4. Point a light wallet client to this server
|
||||
You should start seeing the frontend ingest and cache the DragonX blocks after ~15 seconds.
|
||||
|
||||
## Lightwalletd Command-line Options
|
||||
|
||||
@@ -115,7 +104,7 @@ These are the current different command line options for lightwalletd:
|
||||
|
||||
| CLI option | Default | What it does |
|
||||
|------------|:--------------:|------------------------------:|
|
||||
| -bind-addr | 127.0.0.1:9067 | address and port to listen on |
|
||||
| -bind-addr | 127.0.0.1:9069 | address and port to listen on |
|
||||
| -tls-cert | blank | the path to a TLS certificate |
|
||||
| -tls-key | blank | the path to a TLS key file |
|
||||
| -no-tls | false | Disable TLS, serve un-encrypted traffic |
|
||||
@@ -125,7 +114,12 @@ These are the current different command line options for lightwalletd:
|
||||
| -cache-size| 40000 | number of blocks to hold in the cache |
|
||||
|
||||
## Support
|
||||
For support or other questions, join us on [Telegram](https://hush.is/telegram), or tweet at [@MyHushTeam](https://twitter.com/MyHushTeam), or toot at our [Mastodon](https://fosstodon.org/@myhushteam) or join [Telegram Support](https://hush.is/telegram_support).
|
||||
For support or other questions, join us on [Telegram](https://hush.is/telegram) or join [Telegram Support](https://hush.is/telegram_support).
|
||||
|
||||
## License
|
||||
GPLv3 or later
|
||||
|
||||
# Copyright
|
||||
|
||||
2016-2022 The Hush Developers
|
||||
2024-2026 The DragonX Developers
|
||||
|
||||
@@ -77,32 +77,41 @@ func loggerFromContext(ctx context.Context) *logrus.Entry {
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
bindAddr string `json:"bind_address,omitempty"`
|
||||
tlsCertPath string `json:"tls_cert_path,omitempty"`
|
||||
tlsKeyPath string `json:"tls_cert_key,omitempty"`
|
||||
noTLS bool `json:no_tls,omitempty`
|
||||
logLevel uint64 `json:"log_level,omitempty"`
|
||||
logPath string `json:"log_file,omitempty"`
|
||||
hush3ConfPath string `json:"hush3_conf,omitempty"`
|
||||
cacheSize int `json:"hush3_conf,omitempty"`
|
||||
bindAddr string `json:"bind_address,omitempty"`
|
||||
tlsCertPath string `json:"tls_cert_path,omitempty"`
|
||||
tlsKeyPath string `json:"tls_cert_key,omitempty"`
|
||||
noTLS bool `json:no_tls,omitempty`
|
||||
logLevel uint64 `json:"log_level,omitempty"`
|
||||
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() {
|
||||
var version = "0.1.1" // set version number
|
||||
|
||||
opts := &Options{}
|
||||
flag.StringVar(&opts.bindAddr, "bind-addr", "127.0.0.1:9067", "the address to listen on")
|
||||
flag.StringVar(&opts.bindAddr, "bind-addr", "127.0.0.1:9069", "the address to listen on")
|
||||
flag.StringVar(&opts.tlsCertPath, "tls-cert", "", "the path to a TLS certificate (optional)")
|
||||
flag.StringVar(&opts.tlsKeyPath, "tls-key", "", "the path to a TLS key file (optional)")
|
||||
flag.BoolVar(&opts.noTLS, "no-tls", false, "Disable TLS, serve un-encrypted traffic.")
|
||||
flag.Uint64Var(&opts.logLevel, "log-level", uint64(logrus.InfoLevel), "log level (logrus 1-7)")
|
||||
flag.StringVar(&opts.logPath, "log-file", "", "log file to write to")
|
||||
flag.StringVar(&opts.hush3ConfPath, "conf-file", "", "conf file to pull RPC creds from")
|
||||
flag.IntVar(&opts.cacheSize, "cache-size", 40000, "number of blocks to hold in the cache")
|
||||
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") {
|
||||
fmt.Printf("Hush lightwalletd version " + version + "\n")
|
||||
fmt.Printf("DragonX lightwalletd version " + version + "\n")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -110,14 +119,14 @@ func main() {
|
||||
// TODO support config from file and env vars
|
||||
flag.Parse()
|
||||
|
||||
if opts.hush3ConfPath == "" {
|
||||
if opts.confPath == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !opts.noTLS && (opts.tlsCertPath == "" || opts.tlsKeyPath == "") {
|
||||
println("Please specify a TLS certificate/key to use. You can use a self-signed certificate.")
|
||||
println("See https://git.hush.is/hush/lightwalletd/src/branch/master/README.md#running-your-own-sdl-lightwalletd")
|
||||
println("See https://git.hush.is/hush/lightwalletd/src/branch/master/README.md")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -159,17 +168,17 @@ func main() {
|
||||
reflection.Register(server)
|
||||
}
|
||||
|
||||
// Initialize Hush RPC client. Right now (Jan 2018) this is only for
|
||||
// Initialize DragonX RPC client. Right now this is only for
|
||||
// sending transactions, but in the future it could back a different type
|
||||
// of block streamer.
|
||||
|
||||
rpcClient, err := frontend.NewZRPCFromConf(opts.hush3ConfPath)
|
||||
rpcClient, err := frontend.NewZRPCFromConf(opts.confPath)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
}).Warn("HUSH3.conf failed, will try empty credentials for rpc")
|
||||
}).Warn("DRAGONX.conf failed, will try empty credentials for rpc")
|
||||
|
||||
rpcClient, err = frontend.NewZRPCFromCreds("127.0.0.1:18031", "", "")
|
||||
rpcClient, err = frontend.NewZRPCFromCreds("127.0.0.1:21769", "", "")
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
@@ -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)
|
||||
|
||||
|
||||
112
common/cache.go
112
common/cache.go
@@ -3,6 +3,7 @@ package common
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.hush.is/hush/lightwalletd/walletrpc"
|
||||
"github.com/golang/protobuf/proto"
|
||||
@@ -22,14 +23,34 @@ type BlockCache struct {
|
||||
m map[int]*BlockCacheEntry
|
||||
|
||||
mutex sync.RWMutex
|
||||
|
||||
// Adaptive confirmation lag. lightwalletd advertises a tip that trails the
|
||||
// real tip by a number of blocks that scales with the recent reorg rate, so
|
||||
// wallets anchor shielded spends at a settled height while the chain is
|
||||
// churning, and at (near) the real tip when it is stable. reorgTimes holds
|
||||
// the times of recent reorg detections, pruned to lagWindow.
|
||||
reorgTimes []time.Time
|
||||
lagWindow time.Duration
|
||||
lagMin int
|
||||
lagMax int
|
||||
adaptiveLag bool
|
||||
|
||||
// advTip is the cached advertised (lag-adjusted) tip, kept fresh by Add so
|
||||
// the per-block serving cap (HeightAllowed) is a cheap read. -1 until ready.
|
||||
advTip int
|
||||
}
|
||||
|
||||
func NewBlockCache(maxEntries int) *BlockCache {
|
||||
return &BlockCache{
|
||||
MaxEntries: maxEntries,
|
||||
FirstBlock: -1,
|
||||
LastBlock: -1,
|
||||
m: make(map[int]*BlockCacheEntry),
|
||||
MaxEntries: maxEntries,
|
||||
FirstBlock: -1,
|
||||
LastBlock: -1,
|
||||
m: make(map[int]*BlockCacheEntry),
|
||||
adaptiveLag: true,
|
||||
lagWindow: 30 * time.Minute,
|
||||
lagMin: 1,
|
||||
lagMax: 12,
|
||||
advTip: -1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +78,8 @@ func (c *BlockCache) Add(height int, block *walletrpc.CompactBlock) (error, bool
|
||||
// Don't allow out-of-order blocks. This is more of a sanity check than anything
|
||||
// If there is a reorg, then the ingestor needs to handle it.
|
||||
if c.m[height-1] != nil && !bytes.Equal(block.PrevHash, c.m[height-1].hash) {
|
||||
// Record the reorg so the adaptive confirmation lag can react to it.
|
||||
c.reorgTimes = append(c.reorgTimes, time.Now())
|
||||
return nil, true
|
||||
}
|
||||
|
||||
@@ -81,6 +104,12 @@ func (c *BlockCache) Add(height int, block *walletrpc.CompactBlock) (error, bool
|
||||
c.FirstBlock = c.FirstBlock + 1
|
||||
}
|
||||
|
||||
// Keep the advertised (lag-adjusted) tip fresh for the block-serving cap.
|
||||
c.advTip = c.LastBlock - c.computeLagLocked()
|
||||
if c.advTip < c.FirstBlock {
|
||||
c.advTip = c.FirstBlock
|
||||
}
|
||||
|
||||
//println("Cache size is ", len(c.m))
|
||||
return nil, false
|
||||
}
|
||||
@@ -116,3 +145,78 @@ func (c *BlockCache) GetLatestBlock() int {
|
||||
|
||||
return c.LastBlock
|
||||
}
|
||||
|
||||
// ConfigureLag sets the adaptive-confirmation-lag parameters. Called once at
|
||||
// startup from the command-line flags.
|
||||
func (c *BlockCache) ConfigureLag(adaptive bool, windowMinutes, min, max int) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.adaptiveLag = adaptive
|
||||
c.lagWindow = time.Duration(windowMinutes) * time.Minute
|
||||
c.lagMin = min
|
||||
c.lagMax = max
|
||||
}
|
||||
|
||||
// computeLagLocked returns the confirmation lag (in blocks) to apply right now
|
||||
// and prunes reorg events that have aged out of the window. The lag is the
|
||||
// configured floor plus the number of reorgs seen within lagWindow, capped at
|
||||
// lagMax. Caller must hold c.mutex.
|
||||
func (c *BlockCache) computeLagLocked() int {
|
||||
if !c.adaptiveLag {
|
||||
return c.lagMin
|
||||
}
|
||||
cutoff := time.Now().Add(-c.lagWindow)
|
||||
kept := c.reorgTimes[:0]
|
||||
for _, t := range c.reorgTimes {
|
||||
if t.After(cutoff) {
|
||||
kept = append(kept, t)
|
||||
}
|
||||
}
|
||||
c.reorgTimes = kept
|
||||
lag := c.lagMin + len(kept)
|
||||
if lag > c.lagMax {
|
||||
lag = c.lagMax
|
||||
}
|
||||
if lag < c.lagMin {
|
||||
lag = c.lagMin
|
||||
}
|
||||
return lag
|
||||
}
|
||||
|
||||
// CurrentLag returns the confirmation lag currently being applied.
|
||||
func (c *BlockCache) CurrentLag() int {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
return c.computeLagLocked()
|
||||
}
|
||||
|
||||
// AdvertisedLatestBlock is the tip height lightwalletd reports to wallets: the
|
||||
// real cache tip minus the adaptive confirmation lag. Wallets sync to and
|
||||
// anchor shielded spends at this settled height, immune to tip reorgs. The
|
||||
// internal cache and ingestor continue to track the real tip.
|
||||
func (c *BlockCache) AdvertisedLatestBlock() int {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
if c.LastBlock < 0 {
|
||||
return c.LastBlock
|
||||
}
|
||||
adv := c.LastBlock - c.computeLagLocked()
|
||||
if adv < c.FirstBlock {
|
||||
adv = c.FirstBlock
|
||||
}
|
||||
c.advTip = adv
|
||||
return adv
|
||||
}
|
||||
|
||||
// HeightAllowed reports whether height is at or below the advertised
|
||||
// (lag-adjusted) tip. lightwalletd refuses to serve blocks above it so wallets
|
||||
// cannot sync or anchor shielded spends into the unstable reorg zone. Cheap
|
||||
// (read lock, no recompute); advTip is kept fresh by Add.
|
||||
func (c *BlockCache) HeightAllowed(height int) bool {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
if c.advTip < 0 {
|
||||
return true
|
||||
}
|
||||
return height <= c.advTip
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func GetSaplingInfo(rpcClient *rpcclient.Client) (int, int, string, string, int,
|
||||
if rpcErr != nil {
|
||||
errParts := strings.SplitN(rpcErr.Error(), ":", 2)
|
||||
errCode, err = strconv.ParseInt(errParts[0], 10, 32)
|
||||
//Check to see if we are requesting a height the hushd doesn't have yet
|
||||
//Check to see if we are requesting a height the dragonxd doesn't have yet
|
||||
if err == nil && errCode == -8 {
|
||||
return -1, -1, "", "", -1, -1, -1, nil
|
||||
}
|
||||
@@ -40,17 +40,34 @@ func GetSaplingInfo(rpcClient *rpcclient.Client) (int, int, string, string, int,
|
||||
|
||||
chainName := f.(map[string]interface{})["chain"].(string)
|
||||
|
||||
upgradeJSON := f.(map[string]interface{})["upgrades"]
|
||||
saplingJSON := upgradeJSON.(map[string]interface{})["76b809bb"] // Sapling ID
|
||||
saplingHeight := saplingJSON.(map[string]interface{})["activationheight"].(float64)
|
||||
// DragonX has Sapling active from block 1 but sets NO_ACTIVATION_HEIGHT in
|
||||
// chainparams, so dragonxd omits it from the upgrades map. Fall back to
|
||||
// height 1 when the key is absent.
|
||||
saplingHeight := float64(1)
|
||||
upgradeJSON, ok := f.(map[string]interface{})["upgrades"]
|
||||
if ok {
|
||||
if upgradesMap, ok := upgradeJSON.(map[string]interface{}); ok {
|
||||
if saplingJSON, ok := upgradesMap["76b809bb"]; ok {
|
||||
saplingHeight = saplingJSON.(map[string]interface{})["activationheight"].(float64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blockHeight := f.(map[string]interface{})["headers"].(float64)
|
||||
difficulty := f.(map[string]interface{})["difficulty"].(float64)
|
||||
longestchain := f.(map[string]interface{})["longestchain"].(float64)
|
||||
notarized := f.(map[string]interface{})["notarized"].(float64)
|
||||
|
||||
consensus := f.(map[string]interface{})["consensus"]
|
||||
branchID := consensus.(map[string]interface{})["nextblock"].(string)
|
||||
// DragonX always uses Sapling consensus rules but CurrentEpochBranchId()
|
||||
// returns Sprout (0) for full nodes because the activation heights are
|
||||
// set to NO_ACTIVATION_HEIGHT. Override to the correct Sapling branch ID.
|
||||
branchID := "76b809bb"
|
||||
consensus, ok := f.(map[string]interface{})["consensus"]
|
||||
if ok {
|
||||
if nextblock, ok := consensus.(map[string]interface{})["nextblock"].(string); ok && nextblock != "00000000" {
|
||||
branchID = nextblock
|
||||
}
|
||||
}
|
||||
|
||||
return int(saplingHeight), int(blockHeight), chainName, branchID, int(difficulty), int(longestchain), int(notarized), nil
|
||||
}
|
||||
@@ -65,7 +82,7 @@ func GetCoinsupply(rpcClient *rpcclient.Client) (string, string, int, int, int,
|
||||
if rpcErr != nil {
|
||||
errParts := strings.SplitN(rpcErr.Error(), ":", 2)
|
||||
errCode, err = strconv.ParseInt(errParts[0], 10, 32)
|
||||
//Check to see if we are requesting a height the hushd doesn't have yet
|
||||
//Check to see if we are requesting a height the dragonxd doesn't have yet
|
||||
if err == nil && errCode == -8 {
|
||||
return "", "", -1, -1, -1, -1, nil
|
||||
}
|
||||
@@ -78,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) {
|
||||
@@ -101,7 +150,7 @@ func getBlockFromRPC(rpcClient *rpcclient.Client, height int) (*walletrpc.Compac
|
||||
if rpcErr != nil {
|
||||
errParts := strings.SplitN(rpcErr.Error(), ":", 2)
|
||||
errCode, err = strconv.ParseInt(errParts[0], 10, 32)
|
||||
//Check to see if we are requesting a height the hushd doesn't have yet
|
||||
//Check to see if we are requesting a height the dragonxd doesn't have yet
|
||||
if err == nil && errCode == -8 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -166,7 +215,7 @@ func BlockIngestor(rpcClient *rpcclient.Client, cache *BlockCache, log *logrus.E
|
||||
if timeoutCount == 3 {
|
||||
log.WithFields(logrus.Fields{
|
||||
"timeouts": timeoutCount,
|
||||
}).Warn("unable to issue RPC call to hushd node 3 times")
|
||||
}).Warn("unable to issue RPC call to dragonxd node 3 times")
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -208,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 {
|
||||
@@ -241,6 +297,12 @@ func GetBlockRange(rpcClient *rpcclient.Client, cache *BlockCache,
|
||||
return
|
||||
}
|
||||
|
||||
if block == nil {
|
||||
errOut <- errors.New(
|
||||
fmt.Sprintf("Block %d was nil without error", i))
|
||||
return
|
||||
}
|
||||
|
||||
blockOut <- *block
|
||||
}
|
||||
|
||||
|
||||
173
frontend/mempool.go
Normal file
173
frontend/mempool.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"git.hush.is/hush/lightwalletd/common"
|
||||
"git.hush.is/hush/lightwalletd/walletrpc"
|
||||
)
|
||||
|
||||
// mempoolMonitor polls the node's mempool ONCE (regardless of how many wallets are streaming) and
|
||||
// fans each new transaction out to every subscribed GetMempoolStream handler. This replaces
|
||||
// per-connection polling: N connected wallets no longer each hit getrawmempool/getrawtransaction.
|
||||
//
|
||||
// Lifecycle matches the semantics the client's monitor loop expects: while a block is current the
|
||||
// monitor emits each mempool tx to subscribers exactly once; when a new block is mined it resets
|
||||
// and closes all subscriber channels, so each handler returns (closing its stream) and the client
|
||||
// reconnects after re-syncing the block. The poller starts lazily on the first subscription and,
|
||||
// when no clients are connected, idles without touching the node.
|
||||
type mempoolMonitor struct {
|
||||
mu sync.Mutex
|
||||
subs map[int]chan *walletrpc.RawTransaction
|
||||
nextID int
|
||||
seen map[string]bool // txids already emitted for the current block
|
||||
ordered []*walletrpc.RawTransaction // emitted txs in arrival order, replayed to late subscribers
|
||||
}
|
||||
|
||||
var (
|
||||
sharedMempoolMonitor *mempoolMonitor
|
||||
mempoolMonitorOnce sync.Once
|
||||
)
|
||||
|
||||
// getMempoolMonitor returns the process-wide mempool monitor, starting its poller on first use.
|
||||
// The client/cache/log are bound once (they are process singletons on the SqlStreamer).
|
||||
func getMempoolMonitor(client *rpcclient.Client, cache *common.BlockCache, log *logrus.Entry) *mempoolMonitor {
|
||||
mempoolMonitorOnce.Do(func() {
|
||||
sharedMempoolMonitor = &mempoolMonitor{
|
||||
subs: make(map[int]chan *walletrpc.RawTransaction),
|
||||
seen: make(map[string]bool),
|
||||
}
|
||||
go sharedMempoolMonitor.run(client, cache, log)
|
||||
})
|
||||
return sharedMempoolMonitor
|
||||
}
|
||||
|
||||
// subscribe registers a stream. It returns the subscriber id, a channel of subsequent mempool txs,
|
||||
// and a snapshot of txs already emitted this block (to be sent first). Registration and snapshot
|
||||
// are taken atomically, so every tx reaches a subscriber exactly once (snapshot xor channel).
|
||||
func (m *mempoolMonitor) subscribe() (int, chan *walletrpc.RawTransaction, []*walletrpc.RawTransaction) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
id := m.nextID
|
||||
m.nextID++
|
||||
ch := make(chan *walletrpc.RawTransaction, 256)
|
||||
m.subs[id] = ch
|
||||
snapshot := make([]*walletrpc.RawTransaction, len(m.ordered))
|
||||
copy(snapshot, m.ordered)
|
||||
return id, ch, snapshot
|
||||
}
|
||||
|
||||
// unsubscribe removes a stream. Safe to call after a new-block reset already dropped it (the map
|
||||
// lookup guards against a double close).
|
||||
func (m *mempoolMonitor) unsubscribe(id int) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if ch, ok := m.subs[id]; ok {
|
||||
delete(m.subs, id)
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
|
||||
// reset is called when a new block is mined: forget this block's txs and close every subscriber
|
||||
// stream so clients re-sync the block and reconnect.
|
||||
func (m *mempoolMonitor) reset() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.seen = make(map[string]bool)
|
||||
m.ordered = nil
|
||||
for id, ch := range m.subs {
|
||||
delete(m.subs, id)
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mempoolMonitor) run(client *rpcclient.Client, cache *common.BlockCache, log *logrus.Entry) {
|
||||
lastHeight := cache.GetLatestBlock()
|
||||
for {
|
||||
// Track block height even while idle, so a new subscriber isn't immediately closed by a
|
||||
// height that advanced during the idle period.
|
||||
if h := cache.GetLatestBlock(); h != lastHeight {
|
||||
lastHeight = h
|
||||
m.reset()
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
idle := len(m.subs) == 0
|
||||
m.mu.Unlock()
|
||||
if idle {
|
||||
// No one is listening; don't touch the node until a client subscribes.
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
height := uint64(lastHeight + 1)
|
||||
|
||||
// List the current mempool txids: getrawmempool(false) -> ["txid", ...].
|
||||
mpParams := []json.RawMessage{json.RawMessage("false")}
|
||||
result, rpcErr := client.RawRequest("getrawmempool", mpParams)
|
||||
if rpcErr != nil {
|
||||
log.Warningf("mempool monitor: getrawmempool failed: %s", rpcErr.Error())
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
var txids []string
|
||||
if err := json.Unmarshal(result, &txids); err != nil {
|
||||
log.Warningf("mempool monitor: cannot parse getrawmempool: %s", err.Error())
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, txid := range txids {
|
||||
m.mu.Lock()
|
||||
already := m.seen[txid]
|
||||
m.mu.Unlock()
|
||||
if already {
|
||||
continue
|
||||
}
|
||||
|
||||
// Fetch the full serialized tx: getrawtransaction("txid") -> hex. Full bytes are
|
||||
// required so shielded memos survive (a CompactTx's 52-byte prefix would not carry a
|
||||
// memo). The mempool txid is already in RPC display byte order, so (unlike
|
||||
// GetTransaction) no reversal is needed.
|
||||
txParams := []json.RawMessage{json.RawMessage("\"" + txid + "\"")}
|
||||
txResult, txErr := client.RawRequest("getrawtransaction", txParams)
|
||||
if txErr != nil {
|
||||
// The tx may have been mined or evicted between listing and fetch; skip it.
|
||||
continue
|
||||
}
|
||||
var txhex string
|
||||
if err := json.Unmarshal(txResult, &txhex); err != nil {
|
||||
continue
|
||||
}
|
||||
txBytes, err := hex.DecodeString(txhex)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
rtx := &walletrpc.RawTransaction{Data: txBytes, Height: height}
|
||||
|
||||
// Record + fan out under the lock. The send is non-blocking (default case), so a slow
|
||||
// subscriber never stalls the poller or the other subscribers.
|
||||
m.mu.Lock()
|
||||
if !m.seen[txid] {
|
||||
m.seen[txid] = true
|
||||
m.ordered = append(m.ordered, rtx)
|
||||
for _, ch := range m.subs {
|
||||
select {
|
||||
case ch <- rtx:
|
||||
default:
|
||||
// Slow subscriber; drop. Block sync will deliver the confirmed tx later.
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,13 @@ func NewZRPCFromConf(confPath string) (*rpcclient.Client, error) {
|
||||
}
|
||||
|
||||
func NewZRPCFromCreds(addr, username, password string) (*rpcclient.Client, error) {
|
||||
// Connect to local hush RPC server using HTTP POST mode.
|
||||
// Connect to local DragonX RPC server using HTTP POST mode.
|
||||
connCfg := &rpcclient.ConnConfig{
|
||||
Host: addr,
|
||||
User: username,
|
||||
Pass: password,
|
||||
HTTPPostMode: true, // Hush only supports HTTP POST mode
|
||||
DisableTLS: true, // Hush does not provide TLS by default
|
||||
HTTPPostMode: true, // DragonX only supports HTTP POST mode
|
||||
DisableTLS: true, // DragonX does not provide TLS by default
|
||||
}
|
||||
// Notice the notification parameter is nil since notifications are
|
||||
// not supported in HTTP POST mode.
|
||||
|
||||
@@ -41,7 +41,13 @@ func (s *SqlStreamer) GetCache() *common.BlockCache {
|
||||
}
|
||||
|
||||
func (s *SqlStreamer) GetLatestBlock(ctx context.Context, placeholder *walletrpc.ChainSpec) (*walletrpc.BlockID, error) {
|
||||
latestBlock := s.cache.GetLatestBlock()
|
||||
// Advertise a tip that trails the real tip by the adaptive confirmation lag,
|
||||
// so wallets anchor shielded spends at a settled height during reorg churn.
|
||||
latestBlock := s.cache.AdvertisedLatestBlock()
|
||||
s.log.WithFields(logrus.Fields{
|
||||
"latestBlock": latestBlock,
|
||||
"adaptiveLag": s.cache.CurrentLag(),
|
||||
}).Info("GetLatestBlock called")
|
||||
|
||||
if latestBlock == -1 {
|
||||
return nil, errors.New("Cache is empty. Server is probably not yet ready.")
|
||||
@@ -76,7 +82,7 @@ func (s *SqlStreamer) GetAddressTxids(addressBlockFilter *walletrpc.TransparentA
|
||||
s.log.Errorf("Got error: %s", rpcErr.Error())
|
||||
errParts := strings.SplitN(rpcErr.Error(), ":", 2)
|
||||
errCode, err = strconv.ParseInt(errParts[0], 10, 32)
|
||||
//Check to see if we are requesting a height the hushd doesn't have yet
|
||||
//Check to see if we are requesting a height the dragonxd doesn't have yet
|
||||
if err == nil && errCode == -8 {
|
||||
return nil
|
||||
}
|
||||
@@ -113,6 +119,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
|
||||
@@ -136,21 +181,47 @@ func (s *SqlStreamer) GetBlock(ctx context.Context, id *walletrpc.BlockID) (*wal
|
||||
}
|
||||
|
||||
func (s *SqlStreamer) GetBlockRange(span *walletrpc.BlockRange, resp walletrpc.CompactTxStreamer_GetBlockRangeServer) error {
|
||||
s.log.WithFields(logrus.Fields{
|
||||
"start": span.Start.Height,
|
||||
"end": span.End.Height,
|
||||
}).Info("GetBlockRange called")
|
||||
|
||||
blockChan := make(chan walletrpc.CompactBlock)
|
||||
errChan := make(chan error)
|
||||
|
||||
go common.GetBlockRange(s.client, s.cache, blockChan, errChan, int(span.Start.Height), int(span.End.Height))
|
||||
|
||||
blockCount := 0
|
||||
for {
|
||||
select {
|
||||
case err := <-errChan:
|
||||
// this will also catch context.DeadlineExceeded from the timeout
|
||||
if err != nil {
|
||||
s.log.WithFields(logrus.Fields{
|
||||
"start": span.Start.Height,
|
||||
"end": span.End.Height,
|
||||
"blocksSent": blockCount,
|
||||
"error": err,
|
||||
}).Error("GetBlockRange error")
|
||||
} else {
|
||||
s.log.WithFields(logrus.Fields{
|
||||
"start": span.Start.Height,
|
||||
"end": span.End.Height,
|
||||
"blocksSent": blockCount,
|
||||
}).Info("GetBlockRange completed")
|
||||
}
|
||||
return err
|
||||
case cBlock := <-blockChan:
|
||||
err := resp.Send(&cBlock)
|
||||
if err != nil {
|
||||
s.log.WithFields(logrus.Fields{
|
||||
"start": span.Start.Height,
|
||||
"end": span.End.Height,
|
||||
"blocksSent": blockCount,
|
||||
"error": err,
|
||||
}).Error("GetBlockRange send error")
|
||||
return err
|
||||
}
|
||||
blockCount++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +252,7 @@ func (s *SqlStreamer) GetTransaction(ctx context.Context, txf *walletrpc.TxFilte
|
||||
s.log.Errorf("Got error: %s", rpcErr.Error())
|
||||
errParts := strings.SplitN(rpcErr.Error(), ":", 2)
|
||||
errCode, err = strconv.ParseInt(errParts[0], 10, 32)
|
||||
//Check to see if we are requesting a height the hushd doesn't have yet
|
||||
//Check to see if we are requesting a height the dragonxd doesn't have yet
|
||||
if err == nil && errCode == -8 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -211,7 +282,7 @@ func (s *SqlStreamer) GetTransaction(ctx context.Context, txf *walletrpc.TxFilte
|
||||
s.log.Errorf("Got error: %s", rpcErr.Error())
|
||||
errParts := strings.SplitN(rpcErr.Error(), ":", 2)
|
||||
errCode, err = strconv.ParseInt(errParts[0], 10, 32)
|
||||
//Check to see if we are requesting a height the hushd doesn't have yet
|
||||
//Check to see if we are requesting a height the dragonxd doesn't have yet
|
||||
if err == nil && errCode == -8 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -239,6 +310,13 @@ func (s *SqlStreamer) GetTransaction(ctx context.Context, txf *walletrpc.TxFilte
|
||||
func (s *SqlStreamer) GetLightdInfo(ctx context.Context, in *walletrpc.Empty) (*walletrpc.LightdInfo, error) {
|
||||
saplingHeight, blockHeight, chainName, consensusBranchId, difficulty, longestchain, notarized, err := common.GetSaplingInfo(s.client)
|
||||
|
||||
s.log.WithFields(logrus.Fields{
|
||||
"saplingHeight": saplingHeight,
|
||||
"blockHeight": blockHeight,
|
||||
"chainName": chainName,
|
||||
"consensusBranchId": consensusBranchId,
|
||||
}).Info("GetLightdInfo called")
|
||||
|
||||
if err != nil {
|
||||
s.log.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
@@ -246,16 +324,23 @@ func (s *SqlStreamer) GetLightdInfo(ctx context.Context, in *walletrpc.Empty) (*
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Report the advertised (lag-adjusted) tip so wallets consider themselves
|
||||
// synced at the height they are actually served, not perpetually N behind.
|
||||
advHeight := s.cache.AdvertisedLatestBlock()
|
||||
if advHeight < 0 {
|
||||
advHeight = blockHeight
|
||||
}
|
||||
|
||||
// TODO these are called Error but they aren't at the moment.
|
||||
// A success will return code 0 and message txhash.
|
||||
return &walletrpc.LightdInfo{
|
||||
Version: "0.1.1-hushlightd",
|
||||
Vendor: "Silentdragonlite LightWalletD",
|
||||
Version: "0.1.1-dragonxlightd",
|
||||
Vendor: "DragonX LightWalletD",
|
||||
TaddrSupport: true,
|
||||
ChainName: chainName,
|
||||
SaplingActivationHeight: uint64(saplingHeight),
|
||||
ConsensusBranchId: consensusBranchId,
|
||||
BlockHeight: uint64(blockHeight),
|
||||
BlockHeight: uint64(advHeight),
|
||||
Difficulty: uint64(difficulty),
|
||||
Longestchain: uint64(longestchain),
|
||||
Notarized: uint64(notarized),
|
||||
@@ -285,7 +370,7 @@ func (s *SqlStreamer) GetCoinsupply(ctx context.Context, in *walletrpc.Empty) (*
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SendTransaction forwards raw transaction bytes to a hushd instance over JSON-RPC
|
||||
// SendTransaction forwards raw transaction bytes to a dragonxd instance over JSON-RPC
|
||||
func (s *SqlStreamer) SendTransaction(ctx context.Context, rawtx *walletrpc.RawTransaction) (*walletrpc.SendResponse, error) {
|
||||
// sendrawtransaction "hexstring" ( allowhighfees )
|
||||
//
|
||||
|
||||
123
monitor_lwd.sh
Executable file
123
monitor_lwd.sh
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2024-2026 The DragonX Developers
|
||||
# Released under GPLv3
|
||||
#
|
||||
# Monitors lightwalletd and restarts it automatically if it crashes.
|
||||
# Usage: ./monitor_lwd.sh &
|
||||
# or: nohup ./monitor_lwd.sh >> /tmp/lwd-monitor.log 2>&1 &
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
LWD_BIN="$SCRIPT_DIR/lightwalletd"
|
||||
LWD_ARGS="-bind-addr lite.dragonx.is:9069 -conf-file $HOME/.hush/DRAGONX/DRAGONX.conf -no-tls -lag-min 4 -lag-max 12 -lag-window 30"
|
||||
LOGFILE="$SCRIPT_DIR/lwd-monitor.log"
|
||||
PIDFILE="/tmp/lwd-monitor.pid"
|
||||
RESTART_DELAY=5 # seconds to wait before restarting after a crash
|
||||
MAX_RAPID_RESTARTS=5 # max restarts within the rapid window before backing off
|
||||
RAPID_WINDOW=120 # seconds — if this many restarts happen within this window, back off
|
||||
BACKOFF_DELAY=60 # seconds to wait when backing off
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() {
|
||||
echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
log "${YELLOW}Monitor shutting down...${NC}"
|
||||
if [[ -n "${LWD_PID:-}" ]] && kill -0 "$LWD_PID" 2>/dev/null; then
|
||||
log "Stopping lightwalletd (PID $LWD_PID)..."
|
||||
kill "$LWD_PID" 2>/dev/null || true
|
||||
wait "$LWD_PID" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$PIDFILE"
|
||||
log "Monitor stopped."
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap cleanup SIGINT SIGTERM
|
||||
|
||||
# Prevent duplicate monitors
|
||||
if [[ -f "$PIDFILE" ]]; then
|
||||
OLD_PID=$(cat "$PIDFILE")
|
||||
if kill -0 "$OLD_PID" 2>/dev/null; then
|
||||
echo "Monitor already running (PID $OLD_PID). Exiting."
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$PIDFILE"
|
||||
fi
|
||||
echo $$ > "$PIDFILE"
|
||||
|
||||
# Check binary exists
|
||||
if [[ ! -x "$LWD_BIN" ]]; then
|
||||
log "${RED}ERROR: lightwalletd binary not found at $LWD_BIN${NC}"
|
||||
log "Build it first with: make build"
|
||||
rm -f "$PIDFILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check conf file exists
|
||||
CONF_FILE="$HOME/.hush/DRAGONX/DRAGONX.conf"
|
||||
if [[ ! -f "$CONF_FILE" ]]; then
|
||||
log "${RED}ERROR: DRAGONX.conf not found at $CONF_FILE${NC}"
|
||||
rm -f "$PIDFILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "${GREEN}DragonX lightwalletd monitor started (PID $$)${NC}"
|
||||
log "Binary: $LWD_BIN"
|
||||
log "Args: $LWD_ARGS"
|
||||
|
||||
restart_times=()
|
||||
LWD_PID=""
|
||||
|
||||
while true; do
|
||||
# Start lightwalletd
|
||||
log "${GREEN}Starting lightwalletd...${NC}"
|
||||
$LWD_BIN $LWD_ARGS >> "$LOGFILE" 2>&1 &
|
||||
LWD_PID=$!
|
||||
log "lightwalletd started with PID $LWD_PID"
|
||||
|
||||
# Wait for it to exit
|
||||
wait "$LWD_PID" || true
|
||||
EXIT_CODE=$?
|
||||
LWD_PID=""
|
||||
|
||||
if [[ $EXIT_CODE -eq 0 ]]; then
|
||||
log "${YELLOW}lightwalletd exited cleanly (code 0). Not restarting.${NC}"
|
||||
break
|
||||
fi
|
||||
|
||||
log "${RED}lightwalletd crashed with exit code $EXIT_CODE${NC}"
|
||||
|
||||
# Track restart frequency for backoff
|
||||
NOW=$(date +%s)
|
||||
restart_times+=("$NOW")
|
||||
|
||||
# Trim old entries outside the rapid window
|
||||
CUTOFF=$((NOW - RAPID_WINDOW))
|
||||
filtered=()
|
||||
for t in "${restart_times[@]}"; do
|
||||
if (( t >= CUTOFF )); then
|
||||
filtered+=("$t")
|
||||
fi
|
||||
done
|
||||
restart_times=("${filtered[@]}")
|
||||
|
||||
if (( ${#restart_times[@]} >= MAX_RAPID_RESTARTS )); then
|
||||
log "${YELLOW}Too many restarts (${#restart_times[@]} in ${RAPID_WINDOW}s). Backing off for ${BACKOFF_DELAY}s...${NC}"
|
||||
sleep "$BACKOFF_DELAY"
|
||||
restart_times=()
|
||||
else
|
||||
log "Restarting in ${RESTART_DELAY}s..."
|
||||
sleep "$RESTART_DELAY"
|
||||
fi
|
||||
done
|
||||
|
||||
rm -f "$PIDFILE"
|
||||
log "Monitor exiting."
|
||||
@@ -100,11 +100,11 @@ func (b *Block) GetPrevHash() []byte {
|
||||
|
||||
func (b *Block) ToCompact() *walletrpc.CompactBlock {
|
||||
compactBlock := &walletrpc.CompactBlock{
|
||||
//TODO ProtoVersion: 1,
|
||||
Height: uint64(b.GetHeight()),
|
||||
PrevHash: b.hdr.HashPrevBlock,
|
||||
Hash: b.GetEncodableHash(),
|
||||
Time: b.hdr.Time,
|
||||
ProtoVersion: 1,
|
||||
Height: uint64(b.GetHeight()),
|
||||
PrevHash: b.hdr.HashPrevBlock,
|
||||
Hash: b.GetEncodableHash(),
|
||||
Time: b.hdr.Time,
|
||||
}
|
||||
|
||||
// Only Sapling transactions have a meaningful compact encoding
|
||||
|
||||
6
start.sh
6
start.sh
@@ -6,7 +6,9 @@
|
||||
# you can choose either IPv4 or IPv6
|
||||
|
||||
# using ipv4 localhost
|
||||
#./lightwalletd -bind-addr localhost:9067 -conf-file ~/.komodo/HUSH3/HUSH3.conf -no-tls
|
||||
#./lightwalletd -bind-addr localhost:9067 -conf-file ~/.hush/HUSH3/HUSH3.conf -no-tls
|
||||
|
||||
# using ipv6 localhost
|
||||
./lightwalletd -bind-addr ip6-localhost:9067 -conf-file ~/.komodo/HUSH3/HUSH3.conf -no-tls
|
||||
#./lightwalletd -bind-addr ip6-localhost:9067 -conf-file ~/.hush/HUSH3/HUSH3.conf -no-tls
|
||||
|
||||
./lightwalletd -bind-addr lite.dragonx.is:9069 -conf-file ~/.hush/DRAGONX/DRAGONX.conf -no-tls &
|
||||
|
||||
6
start_server.sh
Normal file
6
start_server.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# set -e
|
||||
|
||||
|
||||
./lightwalletd -bind-addr lite.dragonx.is:9069 -conf-file ~/.hush/DRAGONX/DRAGONX.conf -no-tls
|
||||
45
stop.sh
Executable file
45
stop.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright 2024-2026 The DragonX Developers
|
||||
# Released under GPLv3
|
||||
#
|
||||
# Stops lightwalletd and its monitor process.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PIDFILE="/tmp/lwd-monitor.pid"
|
||||
|
||||
# Stop the monitor first (if running), which will also stop lightwalletd
|
||||
if [[ -f "$PIDFILE" ]]; then
|
||||
MON_PID=$(cat "$PIDFILE")
|
||||
if kill -0 "$MON_PID" 2>/dev/null; then
|
||||
echo "Stopping monitor (PID $MON_PID)..."
|
||||
kill "$MON_PID"
|
||||
# Wait briefly for cleanup
|
||||
for i in $(seq 1 10); do
|
||||
kill -0 "$MON_PID" 2>/dev/null || break
|
||||
sleep 0.5
|
||||
done
|
||||
echo "Monitor stopped."
|
||||
else
|
||||
echo "Stale monitor pidfile found, removing."
|
||||
rm -f "$PIDFILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Kill any remaining lightwalletd processes
|
||||
LWDPIDS=$(pgrep -f 'lightwalletd.*-conf-file' 2>/dev/null || true)
|
||||
if [[ -n "$LWDPIDS" ]]; then
|
||||
echo "Stopping lightwalletd (PID $LWDPIDS)..."
|
||||
kill $LWDPIDS 2>/dev/null || true
|
||||
sleep 1
|
||||
# Force kill if still running
|
||||
for pid in $LWDPIDS; do
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo "Force killing PID $pid..."
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
echo "lightwalletd stopped."
|
||||
else
|
||||
echo "No lightwalletd process found."
|
||||
fi
|
||||
@@ -23,10 +23,10 @@ echo "+------'+------'+------'+------'+------'+------'+------'+------'+------'+-
|
||||
|
||||
# now to compiling...
|
||||
echo ""
|
||||
echo "You have go installed, so starting to compile Hush lightwalletd for you..."
|
||||
echo "You have go installed, so starting to compile DragonX lightwalletd for you..."
|
||||
cd `pwd`/cmd/server
|
||||
go build -o lightwalletd main.go
|
||||
mv lightwalletd `pwd`/../../lightwalletd
|
||||
echo ""
|
||||
echo "Hush lightwalletd is now compiled for you. Enjoy and reach out if you need support."
|
||||
echo "DragonX lightwalletd is now compiled for you. Enjoy and reach out if you need support."
|
||||
echo "For options, run ./lightwalletd --help"
|
||||
|
||||
BIN
walletrpc/__pycache__/compact_formats_pb2.cpython-310.pyc
Normal file
BIN
walletrpc/__pycache__/compact_formats_pb2.cpython-310.pyc
Normal file
Binary file not shown.
BIN
walletrpc/__pycache__/service_pb2.cpython-310.pyc
Normal file
BIN
walletrpc/__pycache__/service_pb2.cpython-310.pyc
Normal file
Binary file not shown.
BIN
walletrpc/__pycache__/service_pb2_grpc.cpython-310.pyc
Normal file
BIN
walletrpc/__pycache__/service_pb2_grpc.cpython-310.pyc
Normal file
Binary file not shown.
43
walletrpc/compact_formats_pb2.py
Normal file
43
walletrpc/compact_formats_pb2.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: compact_formats.proto
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
'compact_formats.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x63ompact_formats.proto\x12\x15\x63\x61sh.z.wallet.sdk.rpc\"\xa1\x01\n\x0c\x43ompactBlock\x12\x14\n\x0cprotoVersion\x18\x01 \x01(\r\x12\x0e\n\x06height\x18\x02 \x01(\x04\x12\x0c\n\x04hash\x18\x03 \x01(\x0c\x12\x10\n\x08prevHash\x18\x04 \x01(\x0c\x12\x0c\n\x04time\x18\x05 \x01(\r\x12\x0e\n\x06header\x18\x06 \x01(\x0c\x12-\n\x03vtx\x18\x07 \x03(\x0b\x32 .cash.z.wallet.sdk.rpc.CompactTx\"\xa1\x01\n\tCompactTx\x12\r\n\x05index\x18\x01 \x01(\x04\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\x12\x0b\n\x03\x66\x65\x65\x18\x03 \x01(\r\x12\x33\n\x06spends\x18\x04 \x03(\x0b\x32#.cash.z.wallet.sdk.rpc.CompactSpend\x12\x35\n\x07outputs\x18\x05 \x03(\x0b\x32$.cash.z.wallet.sdk.rpc.CompactOutput\"\x1a\n\x0c\x43ompactSpend\x12\n\n\x02nf\x18\x01 \x01(\x0c\"=\n\rCompactOutput\x12\x0b\n\x03\x63mu\x18\x01 \x01(\x0c\x12\x0b\n\x03\x65pk\x18\x02 \x01(\x0c\x12\x12\n\nciphertext\x18\x03 \x01(\x0c\x42\x0bZ\twalletrpcb\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'compact_formats_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
_globals['DESCRIPTOR']._loaded_options = None
|
||||
_globals['DESCRIPTOR']._serialized_options = b'Z\twalletrpc'
|
||||
_globals['_COMPACTBLOCK']._serialized_start=49
|
||||
_globals['_COMPACTBLOCK']._serialized_end=210
|
||||
_globals['_COMPACTTX']._serialized_start=213
|
||||
_globals['_COMPACTTX']._serialized_end=374
|
||||
_globals['_COMPACTSPEND']._serialized_start=376
|
||||
_globals['_COMPACTSPEND']._serialized_end=402
|
||||
_globals['_COMPACTOUTPUT']._serialized_start=404
|
||||
_globals['_COMPACTOUTPUT']._serialized_end=465
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
24
walletrpc/compact_formats_pb2_grpc.py
Normal file
24
walletrpc/compact_formats_pb2_grpc.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
||||
"""Client and server classes corresponding to protobuf-defined services."""
|
||||
import grpc
|
||||
import warnings
|
||||
|
||||
|
||||
GRPC_GENERATED_VERSION = '1.78.0'
|
||||
GRPC_VERSION = grpc.__version__
|
||||
_version_not_supported = False
|
||||
|
||||
try:
|
||||
from grpc._utilities import first_version_is_lower
|
||||
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
|
||||
except ImportError:
|
||||
_version_not_supported = True
|
||||
|
||||
if _version_not_supported:
|
||||
raise RuntimeError(
|
||||
f'The grpc package installed is at version {GRPC_VERSION},'
|
||||
+ ' but the generated code in compact_formats_pb2_grpc.py depends on'
|
||||
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
|
||||
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
|
||||
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
|
||||
)
|
||||
@@ -694,6 +694,8 @@ type CompactTxStreamerClient interface {
|
||||
SendTransaction(ctx context.Context, in *RawTransaction, opts ...grpc.CallOption) (*SendResponse, error)
|
||||
// t-Address support
|
||||
GetAddressTxids(ctx context.Context, in *TransparentAddressBlockFilter, opts ...grpc.CallOption) (CompactTxStreamer_GetAddressTxidsClient, error)
|
||||
// Mempool
|
||||
GetMempoolStream(ctx context.Context, in *Empty, opts ...grpc.CallOption) (CompactTxStreamer_GetMempoolStreamClient, error)
|
||||
// Misc
|
||||
GetLightdInfo(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*LightdInfo, error)
|
||||
GetCoinsupply(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Coinsupply, error)
|
||||
@@ -807,6 +809,38 @@ func (x *compactTxStreamerGetAddressTxidsClient) Recv() (*RawTransaction, error)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *compactTxStreamerClient) GetMempoolStream(ctx context.Context, in *Empty, opts ...grpc.CallOption) (CompactTxStreamer_GetMempoolStreamClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &_CompactTxStreamer_serviceDesc.Streams[2], "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetMempoolStream", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &compactTxStreamerGetMempoolStreamClient{stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type CompactTxStreamer_GetMempoolStreamClient interface {
|
||||
Recv() (*RawTransaction, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type compactTxStreamerGetMempoolStreamClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *compactTxStreamerGetMempoolStreamClient) Recv() (*RawTransaction, error) {
|
||||
m := new(RawTransaction)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *compactTxStreamerClient) GetLightdInfo(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*LightdInfo, error) {
|
||||
out := new(LightdInfo)
|
||||
err := c.cc.Invoke(ctx, "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo", in, out, opts...)
|
||||
@@ -835,6 +869,8 @@ type CompactTxStreamerServer interface {
|
||||
SendTransaction(context.Context, *RawTransaction) (*SendResponse, error)
|
||||
// t-Address support
|
||||
GetAddressTxids(*TransparentAddressBlockFilter, CompactTxStreamer_GetAddressTxidsServer) error
|
||||
// Mempool
|
||||
GetMempoolStream(*Empty, CompactTxStreamer_GetMempoolStreamServer) error
|
||||
// Misc
|
||||
GetLightdInfo(context.Context, *Empty) (*LightdInfo, error)
|
||||
GetCoinsupply(context.Context, *Empty) (*Coinsupply, error)
|
||||
@@ -862,6 +898,9 @@ func (*UnimplementedCompactTxStreamerServer) SendTransaction(ctx context.Context
|
||||
func (*UnimplementedCompactTxStreamerServer) GetAddressTxids(req *TransparentAddressBlockFilter, srv CompactTxStreamer_GetAddressTxidsServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method GetAddressTxids not implemented")
|
||||
}
|
||||
func (*UnimplementedCompactTxStreamerServer) GetMempoolStream(req *Empty, srv CompactTxStreamer_GetMempoolStreamServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method GetMempoolStream not implemented")
|
||||
}
|
||||
func (*UnimplementedCompactTxStreamerServer) GetLightdInfo(ctx context.Context, req *Empty) (*LightdInfo, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetLightdInfo not implemented")
|
||||
}
|
||||
@@ -987,6 +1026,27 @@ func (x *compactTxStreamerGetAddressTxidsServer) Send(m *RawTransaction) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _CompactTxStreamer_GetMempoolStream_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(Empty)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(CompactTxStreamerServer).GetMempoolStream(m, &compactTxStreamerGetMempoolStreamServer{stream})
|
||||
}
|
||||
|
||||
type CompactTxStreamer_GetMempoolStreamServer interface {
|
||||
Send(*RawTransaction) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type compactTxStreamerGetMempoolStreamServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *compactTxStreamerGetMempoolStreamServer) Send(m *RawTransaction) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func _CompactTxStreamer_GetLightdInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Empty)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -1063,6 +1123,11 @@ var _CompactTxStreamer_serviceDesc = grpc.ServiceDesc{
|
||||
Handler: _CompactTxStreamer_GetAddressTxids_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "GetMempoolStream",
|
||||
Handler: _CompactTxStreamer_GetMempoolStream_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "service.proto",
|
||||
}
|
||||
|
||||
@@ -87,6 +87,13 @@ service CompactTxStreamer {
|
||||
// t-Address support
|
||||
rpc GetAddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {}
|
||||
|
||||
// Mempool
|
||||
// Return a stream of current mempool transactions as full RawTransactions. The stream stays
|
||||
// open while there are mempool transactions and is closed when a new block is mined, at which
|
||||
// point the client re-syncs the block and reconnects. Full RawTransactions (not CompactTx) are
|
||||
// required so wallets can read shielded memos of 0-confirmation transactions (e.g. chat).
|
||||
rpc GetMempoolStream(Empty) returns (stream RawTransaction) {}
|
||||
|
||||
// Misc
|
||||
rpc GetLightdInfo(Empty) returns (LightdInfo) {}
|
||||
rpc GetCoinsupply(Empty) returns (Coinsupply) {}
|
||||
|
||||
60
walletrpc/service_pb2.py
Normal file
60
walletrpc/service_pb2.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# NO CHECKED-IN PROTOBUF GENCODE
|
||||
# source: service.proto
|
||||
# Protobuf Python Version: 6.31.1
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import runtime_version as _runtime_version
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
_runtime_version.ValidateProtobufRuntimeVersion(
|
||||
_runtime_version.Domain.PUBLIC,
|
||||
6,
|
||||
31,
|
||||
1,
|
||||
'',
|
||||
'service.proto'
|
||||
)
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
import compact_formats_pb2 as compact__formats__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rservice.proto\x12\x15\x63\x61sh.z.wallet.sdk.rpc\x1a\x15\x63ompact_formats.proto\"\'\n\x07\x42lockID\x12\x0e\n\x06height\x18\x01 \x01(\x04\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\"h\n\nBlockRange\x12-\n\x05start\x18\x01 \x01(\x0b\x32\x1e.cash.z.wallet.sdk.rpc.BlockID\x12+\n\x03\x65nd\x18\x02 \x01(\x0b\x32\x1e.cash.z.wallet.sdk.rpc.BlockID\"V\n\x08TxFilter\x12-\n\x05\x62lock\x18\x01 \x01(\x0b\x32\x1e.cash.z.wallet.sdk.rpc.BlockID\x12\r\n\x05index\x18\x02 \x01(\x04\x12\x0c\n\x04hash\x18\x03 \x01(\x0c\".\n\x0eRawTransaction\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x0e\n\x06height\x18\x02 \x01(\x04\"7\n\x0cSendResponse\x12\x11\n\terrorCode\x18\x01 \x01(\x05\x12\x14\n\x0c\x65rrorMessage\x18\x02 \x01(\t\"\x0b\n\tChainSpec\"\x07\n\x05\x45mpty\"\xe4\x01\n\nLightdInfo\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x0e\n\x06vendor\x18\x02 \x01(\t\x12\x14\n\x0ctaddrSupport\x18\x03 \x01(\x08\x12\x11\n\tchainName\x18\x04 \x01(\t\x12\x1f\n\x17saplingActivationHeight\x18\x05 \x01(\x04\x12\x19\n\x11\x63onsensusBranchId\x18\x06 \x01(\t\x12\x13\n\x0b\x62lockHeight\x18\x07 \x01(\x04\x12\x12\n\ndifficulty\x18\x08 \x01(\x04\x12\x14\n\x0clongestchain\x18\t \x01(\x04\x12\x11\n\tnotarized\x18\n \x01(\x04\"i\n\nCoinsupply\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\x0c\n\x04\x63oin\x18\x02 \x01(\t\x12\x0e\n\x06height\x18\x03 \x01(\x04\x12\x0e\n\x06supply\x18\x04 \x01(\x04\x12\x0e\n\x06zfunds\x18\x05 \x01(\x04\x12\r\n\x05total\x18\x06 \x01(\x04\"%\n\x12TransparentAddress\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\"b\n\x1dTransparentAddressBlockFilter\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x30\n\x05range\x18\x02 \x01(\x0b\x32!.cash.z.wallet.sdk.rpc.BlockRange2\xf2\x05\n\x11\x43ompactTxStreamer\x12T\n\x0eGetLatestBlock\x12 .cash.z.wallet.sdk.rpc.ChainSpec\x1a\x1e.cash.z.wallet.sdk.rpc.BlockID\"\x00\x12Q\n\x08GetBlock\x12\x1e.cash.z.wallet.sdk.rpc.BlockID\x1a#.cash.z.wallet.sdk.rpc.CompactBlock\"\x00\x12[\n\rGetBlockRange\x12!.cash.z.wallet.sdk.rpc.BlockRange\x1a#.cash.z.wallet.sdk.rpc.CompactBlock\"\x00\x30\x01\x12Z\n\x0eGetTransaction\x12\x1f.cash.z.wallet.sdk.rpc.TxFilter\x1a%.cash.z.wallet.sdk.rpc.RawTransaction\"\x00\x12_\n\x0fSendTransaction\x12%.cash.z.wallet.sdk.rpc.RawTransaction\x1a#.cash.z.wallet.sdk.rpc.SendResponse\"\x00\x12r\n\x0fGetAddressTxids\x12\x34.cash.z.wallet.sdk.rpc.TransparentAddressBlockFilter\x1a%.cash.z.wallet.sdk.rpc.RawTransaction\"\x00\x30\x01\x12R\n\rGetLightdInfo\x12\x1c.cash.z.wallet.sdk.rpc.Empty\x1a!.cash.z.wallet.sdk.rpc.LightdInfo\"\x00\x12R\n\rGetCoinsupply\x12\x1c.cash.z.wallet.sdk.rpc.Empty\x1a!.cash.z.wallet.sdk.rpc.Coinsupply\"\x00\x42\x0bZ\twalletrpcb\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'service_pb2', _globals)
|
||||
if not _descriptor._USE_C_DESCRIPTORS:
|
||||
_globals['DESCRIPTOR']._loaded_options = None
|
||||
_globals['DESCRIPTOR']._serialized_options = b'Z\twalletrpc'
|
||||
_globals['_BLOCKID']._serialized_start=63
|
||||
_globals['_BLOCKID']._serialized_end=102
|
||||
_globals['_BLOCKRANGE']._serialized_start=104
|
||||
_globals['_BLOCKRANGE']._serialized_end=208
|
||||
_globals['_TXFILTER']._serialized_start=210
|
||||
_globals['_TXFILTER']._serialized_end=296
|
||||
_globals['_RAWTRANSACTION']._serialized_start=298
|
||||
_globals['_RAWTRANSACTION']._serialized_end=344
|
||||
_globals['_SENDRESPONSE']._serialized_start=346
|
||||
_globals['_SENDRESPONSE']._serialized_end=401
|
||||
_globals['_CHAINSPEC']._serialized_start=403
|
||||
_globals['_CHAINSPEC']._serialized_end=414
|
||||
_globals['_EMPTY']._serialized_start=416
|
||||
_globals['_EMPTY']._serialized_end=423
|
||||
_globals['_LIGHTDINFO']._serialized_start=426
|
||||
_globals['_LIGHTDINFO']._serialized_end=654
|
||||
_globals['_COINSUPPLY']._serialized_start=656
|
||||
_globals['_COINSUPPLY']._serialized_end=761
|
||||
_globals['_TRANSPARENTADDRESS']._serialized_start=763
|
||||
_globals['_TRANSPARENTADDRESS']._serialized_end=800
|
||||
_globals['_TRANSPARENTADDRESSBLOCKFILTER']._serialized_start=802
|
||||
_globals['_TRANSPARENTADDRESSBLOCKFILTER']._serialized_end=900
|
||||
_globals['_COMPACTTXSTREAMER']._serialized_start=903
|
||||
_globals['_COMPACTTXSTREAMER']._serialized_end=1657
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
403
walletrpc/service_pb2_grpc.py
Normal file
403
walletrpc/service_pb2_grpc.py
Normal file
@@ -0,0 +1,403 @@
|
||||
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
||||
"""Client and server classes corresponding to protobuf-defined services."""
|
||||
import grpc
|
||||
import warnings
|
||||
|
||||
import compact_formats_pb2 as compact__formats__pb2
|
||||
import service_pb2 as service__pb2
|
||||
|
||||
GRPC_GENERATED_VERSION = '1.78.0'
|
||||
GRPC_VERSION = grpc.__version__
|
||||
_version_not_supported = False
|
||||
|
||||
try:
|
||||
from grpc._utilities import first_version_is_lower
|
||||
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
|
||||
except ImportError:
|
||||
_version_not_supported = True
|
||||
|
||||
if _version_not_supported:
|
||||
raise RuntimeError(
|
||||
f'The grpc package installed is at version {GRPC_VERSION},'
|
||||
+ ' but the generated code in service_pb2_grpc.py depends on'
|
||||
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
|
||||
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
|
||||
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
|
||||
)
|
||||
|
||||
|
||||
class CompactTxStreamerStub(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def __init__(self, channel):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
channel: A grpc.Channel.
|
||||
"""
|
||||
self.GetLatestBlock = channel.unary_unary(
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock',
|
||||
request_serializer=service__pb2.ChainSpec.SerializeToString,
|
||||
response_deserializer=service__pb2.BlockID.FromString,
|
||||
_registered_method=True)
|
||||
self.GetBlock = channel.unary_unary(
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlock',
|
||||
request_serializer=service__pb2.BlockID.SerializeToString,
|
||||
response_deserializer=compact__formats__pb2.CompactBlock.FromString,
|
||||
_registered_method=True)
|
||||
self.GetBlockRange = channel.unary_stream(
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlockRange',
|
||||
request_serializer=service__pb2.BlockRange.SerializeToString,
|
||||
response_deserializer=compact__formats__pb2.CompactBlock.FromString,
|
||||
_registered_method=True)
|
||||
self.GetTransaction = channel.unary_unary(
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTransaction',
|
||||
request_serializer=service__pb2.TxFilter.SerializeToString,
|
||||
response_deserializer=service__pb2.RawTransaction.FromString,
|
||||
_registered_method=True)
|
||||
self.SendTransaction = channel.unary_unary(
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/SendTransaction',
|
||||
request_serializer=service__pb2.RawTransaction.SerializeToString,
|
||||
response_deserializer=service__pb2.SendResponse.FromString,
|
||||
_registered_method=True)
|
||||
self.GetAddressTxids = channel.unary_stream(
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressTxids',
|
||||
request_serializer=service__pb2.TransparentAddressBlockFilter.SerializeToString,
|
||||
response_deserializer=service__pb2.RawTransaction.FromString,
|
||||
_registered_method=True)
|
||||
self.GetLightdInfo = channel.unary_unary(
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo',
|
||||
request_serializer=service__pb2.Empty.SerializeToString,
|
||||
response_deserializer=service__pb2.LightdInfo.FromString,
|
||||
_registered_method=True)
|
||||
self.GetCoinsupply = channel.unary_unary(
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetCoinsupply',
|
||||
request_serializer=service__pb2.Empty.SerializeToString,
|
||||
response_deserializer=service__pb2.Coinsupply.FromString,
|
||||
_registered_method=True)
|
||||
|
||||
|
||||
class CompactTxStreamerServicer(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
def GetLatestBlock(self, request, context):
|
||||
"""Compact Blocks
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetBlock(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetBlockRange(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetTransaction(self, request, context):
|
||||
"""Transactions
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def SendTransaction(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetAddressTxids(self, request, context):
|
||||
"""t-Address support
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetLightdInfo(self, request, context):
|
||||
"""Misc
|
||||
"""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
def GetCoinsupply(self, request, context):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
||||
context.set_details('Method not implemented!')
|
||||
raise NotImplementedError('Method not implemented!')
|
||||
|
||||
|
||||
def add_CompactTxStreamerServicer_to_server(servicer, server):
|
||||
rpc_method_handlers = {
|
||||
'GetLatestBlock': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetLatestBlock,
|
||||
request_deserializer=service__pb2.ChainSpec.FromString,
|
||||
response_serializer=service__pb2.BlockID.SerializeToString,
|
||||
),
|
||||
'GetBlock': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetBlock,
|
||||
request_deserializer=service__pb2.BlockID.FromString,
|
||||
response_serializer=compact__formats__pb2.CompactBlock.SerializeToString,
|
||||
),
|
||||
'GetBlockRange': grpc.unary_stream_rpc_method_handler(
|
||||
servicer.GetBlockRange,
|
||||
request_deserializer=service__pb2.BlockRange.FromString,
|
||||
response_serializer=compact__formats__pb2.CompactBlock.SerializeToString,
|
||||
),
|
||||
'GetTransaction': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetTransaction,
|
||||
request_deserializer=service__pb2.TxFilter.FromString,
|
||||
response_serializer=service__pb2.RawTransaction.SerializeToString,
|
||||
),
|
||||
'SendTransaction': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.SendTransaction,
|
||||
request_deserializer=service__pb2.RawTransaction.FromString,
|
||||
response_serializer=service__pb2.SendResponse.SerializeToString,
|
||||
),
|
||||
'GetAddressTxids': grpc.unary_stream_rpc_method_handler(
|
||||
servicer.GetAddressTxids,
|
||||
request_deserializer=service__pb2.TransparentAddressBlockFilter.FromString,
|
||||
response_serializer=service__pb2.RawTransaction.SerializeToString,
|
||||
),
|
||||
'GetLightdInfo': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetLightdInfo,
|
||||
request_deserializer=service__pb2.Empty.FromString,
|
||||
response_serializer=service__pb2.LightdInfo.SerializeToString,
|
||||
),
|
||||
'GetCoinsupply': grpc.unary_unary_rpc_method_handler(
|
||||
servicer.GetCoinsupply,
|
||||
request_deserializer=service__pb2.Empty.FromString,
|
||||
response_serializer=service__pb2.Coinsupply.SerializeToString,
|
||||
),
|
||||
}
|
||||
generic_handler = grpc.method_handlers_generic_handler(
|
||||
'cash.z.wallet.sdk.rpc.CompactTxStreamer', rpc_method_handlers)
|
||||
server.add_generic_rpc_handlers((generic_handler,))
|
||||
server.add_registered_method_handlers('cash.z.wallet.sdk.rpc.CompactTxStreamer', rpc_method_handlers)
|
||||
|
||||
|
||||
# This class is part of an EXPERIMENTAL API.
|
||||
class CompactTxStreamer(object):
|
||||
"""Missing associated documentation comment in .proto file."""
|
||||
|
||||
@staticmethod
|
||||
def GetLatestBlock(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock',
|
||||
service__pb2.ChainSpec.SerializeToString,
|
||||
service__pb2.BlockID.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def GetBlock(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlock',
|
||||
service__pb2.BlockID.SerializeToString,
|
||||
compact__formats__pb2.CompactBlock.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def GetBlockRange(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_stream(
|
||||
request,
|
||||
target,
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlockRange',
|
||||
service__pb2.BlockRange.SerializeToString,
|
||||
compact__formats__pb2.CompactBlock.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def GetTransaction(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTransaction',
|
||||
service__pb2.TxFilter.SerializeToString,
|
||||
service__pb2.RawTransaction.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def SendTransaction(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/SendTransaction',
|
||||
service__pb2.RawTransaction.SerializeToString,
|
||||
service__pb2.SendResponse.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def GetAddressTxids(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_stream(
|
||||
request,
|
||||
target,
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressTxids',
|
||||
service__pb2.TransparentAddressBlockFilter.SerializeToString,
|
||||
service__pb2.RawTransaction.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def GetLightdInfo(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo',
|
||||
service__pb2.Empty.SerializeToString,
|
||||
service__pb2.LightdInfo.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
|
||||
@staticmethod
|
||||
def GetCoinsupply(request,
|
||||
target,
|
||||
options=(),
|
||||
channel_credentials=None,
|
||||
call_credentials=None,
|
||||
insecure=False,
|
||||
compression=None,
|
||||
wait_for_ready=None,
|
||||
timeout=None,
|
||||
metadata=None):
|
||||
return grpc.experimental.unary_unary(
|
||||
request,
|
||||
target,
|
||||
'/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetCoinsupply',
|
||||
service__pb2.Empty.SerializeToString,
|
||||
service__pb2.Coinsupply.FromString,
|
||||
options,
|
||||
channel_credentials,
|
||||
insecure,
|
||||
call_credentials,
|
||||
compression,
|
||||
wait_for_ready,
|
||||
timeout,
|
||||
metadata,
|
||||
_registered_method=True)
|
||||
Reference in New Issue
Block a user