jahway603
3 years ago
13 changed files with 905 additions and 39 deletions
@ -0,0 +1,3 @@ |
|||
highlited |
|||
cert.pem |
|||
key.pem |
@ -0,0 +1,3 @@ |
|||
# The Hush Developers |
|||
|
|||
Jahway603 https://git.hush.is/jahway603 https://github.com/jahway603 |
@ -0,0 +1,118 @@ |
|||
package common |
|||
|
|||
import ( |
|||
"bytes" |
|||
"sync" |
|||
|
|||
"git.hush.is/hush/lightwalletd/walletrpc" |
|||
"github.com/golang/protobuf/proto" |
|||
) |
|||
|
|||
type BlockCacheEntry struct { |
|||
data []byte |
|||
hash []byte |
|||
} |
|||
|
|||
type BlockCache struct { |
|||
MaxEntries int |
|||
|
|||
FirstBlock int |
|||
LastBlock int |
|||
|
|||
m map[int]*BlockCacheEntry |
|||
|
|||
mutex sync.RWMutex |
|||
} |
|||
|
|||
func NewBlockCache(maxEntries int) *BlockCache { |
|||
return &BlockCache{ |
|||
MaxEntries: maxEntries, |
|||
FirstBlock: -1, |
|||
LastBlock: -1, |
|||
m: make(map[int]*BlockCacheEntry), |
|||
} |
|||
} |
|||
|
|||
func (c *BlockCache) Add(height int, block *walletrpc.CompactBlock) (error, bool) { |
|||
c.mutex.Lock() |
|||
defer c.mutex.Unlock() |
|||
|
|||
//println("Cache add", height)
|
|||
if c.FirstBlock == -1 && c.LastBlock == -1 { |
|||
// If this is the first block, prep the data structure
|
|||
c.FirstBlock = height |
|||
c.LastBlock = height - 1 |
|||
} |
|||
|
|||
// If we're adding a block in the middle of the cache, remove all
|
|||
// blocks after it, since this might be a reorg, and we don't want
|
|||
// Any outdated blocks returned
|
|||
if height >= c.FirstBlock && height <= c.LastBlock { |
|||
for i := height; i <= c.LastBlock; i++ { |
|||
delete(c.m, i) |
|||
} |
|||
c.LastBlock = height - 1 |
|||
} |
|||
|
|||
// 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) { |
|||
return nil, true |
|||
} |
|||
|
|||
// Add the entry and update the counters
|
|||
data, err := proto.Marshal(block) |
|||
if err != nil { |
|||
println("Error marshalling block!") |
|||
return err, false |
|||
} |
|||
|
|||
c.m[height] = &BlockCacheEntry{ |
|||
data: data, |
|||
hash: block.GetHash(), |
|||
} |
|||
|
|||
c.LastBlock = height |
|||
|
|||
// If the cache is full, remove the oldest block
|
|||
if c.LastBlock-c.FirstBlock+1 > c.MaxEntries { |
|||
//println("Deleteing at height", c.FirstBlock)
|
|||
delete(c.m, c.FirstBlock) |
|||
c.FirstBlock = c.FirstBlock + 1 |
|||
} |
|||
|
|||
//println("Cache size is ", len(c.m))
|
|||
return nil, false |
|||
} |
|||
|
|||
func (c *BlockCache) Get(height int) *walletrpc.CompactBlock { |
|||
c.mutex.RLock() |
|||
defer c.mutex.RUnlock() |
|||
|
|||
//println("Cache get", height)
|
|||
if c.LastBlock == -1 || c.FirstBlock == -1 { |
|||
return nil |
|||
} |
|||
|
|||
if height < c.FirstBlock || height > c.LastBlock { |
|||
//println("Cache miss: index out of range")
|
|||
return nil |
|||
} |
|||
|
|||
//println("Cache returned")
|
|||
serialized := &walletrpc.CompactBlock{} |
|||
err := proto.Unmarshal(c.m[height].data, serialized) |
|||
if err != nil { |
|||
println("Error unmarshalling compact block") |
|||
return nil |
|||
} |
|||
|
|||
return serialized |
|||
} |
|||
|
|||
func (c *BlockCache) GetLatestBlock() int { |
|||
c.mutex.RLock() |
|||
defer c.mutex.RUnlock() |
|||
|
|||
return c.LastBlock |
|||
} |
@ -0,0 +1,277 @@ |
|||
package common |
|||
|
|||
import ( |
|||
"encoding/hex" |
|||
"encoding/json" |
|||
"fmt" |
|||
"strconv" |
|||
"strings" |
|||
"time" |
|||
|
|||
"git.hush.is/hush/lightwalletd/parser" |
|||
"git.hush.is/hush/lightwalletd/walletrpc" |
|||
"github.com/btcsuite/btcd/rpcclient" |
|||
"github.com/pkg/errors" |
|||
"github.com/sirupsen/logrus" |
|||
) |
|||
|
|||
var ( |
|||
Version = "v0.0.1-alpha" |
|||
BuildUser = "jahway603" |
|||
) |
|||
|
|||
// highlightd CLI options
|
|||
type Options struct { |
|||
GRPCBindAddr string `json:"grpc_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"` |
|||
} |
|||
|
|||
func GetSaplingInfo(rpcClient *rpcclient.Client) (int, int, string, string, int, int, int, error) { |
|||
result, rpcErr := rpcClient.RawRequest("getblockchaininfo", make([]json.RawMessage, 0)) |
|||
|
|||
var err error |
|||
var errCode int64 |
|||
|
|||
// For some reason, the error responses are not JSON
|
|||
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
|
|||
if err == nil && errCode == -8 { |
|||
return -1, -1, "", "", -1, -1, -1, nil |
|||
} |
|||
return -1, -1, "", "", -1, -1, -1, errors.Wrap(rpcErr, "error requesting block") |
|||
} |
|||
|
|||
var f interface{} |
|||
err = json.Unmarshal(result, &f) |
|||
if err != nil { |
|||
return -1, -1, "", "", -1, -1, -1, errors.Wrap(err, "error reading JSON response") |
|||
} |
|||
|
|||
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) |
|||
|
|||
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) |
|||
|
|||
return int(saplingHeight), int(blockHeight), chainName, branchID, int(difficulty), int(longestchain), int(notarized), nil |
|||
} |
|||
|
|||
func GetCoinsupply(rpcClient *rpcclient.Client) (string, string, int, int, int, int, error) { |
|||
result1, rpcErr := rpcClient.RawRequest("coinsupply", make([]json.RawMessage, 0)) |
|||
|
|||
var err error |
|||
var errCode int64 |
|||
|
|||
// For some reason, the error responses are not JSON
|
|||
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
|
|||
if err == nil && errCode == -8 { |
|||
return "", "", -1, -1, -1, -1, nil |
|||
} |
|||
return "", "", -1, -1, -1, -1, errors.Wrap(rpcErr, "error requesting coinsupply") |
|||
} |
|||
|
|||
var f interface{} |
|||
err = json.Unmarshal(result1, &f) |
|||
if err != nil { |
|||
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) |
|||
|
|||
return result, coin, int(height), int(supply), int(zfunds), int(total), nil |
|||
} |
|||
|
|||
func getBlockFromRPC(rpcClient *rpcclient.Client, height int) (*walletrpc.CompactBlock, error) { |
|||
params := make([]json.RawMessage, 2) |
|||
params[0] = json.RawMessage("\"" + strconv.Itoa(height) + "\"") |
|||
params[1] = json.RawMessage("0") |
|||
result, rpcErr := rpcClient.RawRequest("getblock", params) |
|||
|
|||
var err error |
|||
var errCode int64 |
|||
|
|||
// For some reason, the error responses are not JSON
|
|||
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
|
|||
if err == nil && errCode == -8 { |
|||
return nil, nil |
|||
} |
|||
return nil, errors.Wrap(rpcErr, "error requesting block") |
|||
} |
|||
|
|||
var blockDataHex string |
|||
err = json.Unmarshal(result, &blockDataHex) |
|||
if err != nil { |
|||
return nil, errors.Wrap(err, "error reading JSON response") |
|||
} |
|||
|
|||
blockData, err := hex.DecodeString(blockDataHex) |
|||
if err != nil { |
|||
return nil, errors.Wrap(err, "error decoding getblock output") |
|||
} |
|||
|
|||
block := parser.NewBlock() |
|||
rest, err := block.ParseFromSlice(blockData) |
|||
if err != nil { |
|||
return nil, errors.Wrap(err, "error parsing block") |
|||
} |
|||
if len(rest) != 0 { |
|||
return nil, errors.New("received overlong message") |
|||
} |
|||
|
|||
return block.ToCompact(), nil |
|||
} |
|||
|
|||
func BlockIngestor(rpcClient *rpcclient.Client, cache *BlockCache, log *logrus.Entry, |
|||
stopChan chan bool, startHeight int) { |
|||
reorgCount := 0 |
|||
height := startHeight |
|||
timeoutCount := 0 |
|||
|
|||
// Start listening for new blocks
|
|||
for { |
|||
select { |
|||
case <-stopChan: |
|||
break |
|||
|
|||
case <-time.After(15 * time.Second): |
|||
for { |
|||
if reorgCount > 0 { |
|||
height -= 10 |
|||
} |
|||
|
|||
if reorgCount > 10 { |
|||
log.Error("Reorg exceeded max of 100 blocks! Help!") |
|||
return |
|||
} |
|||
|
|||
block, err := getBlockFromRPC(rpcClient, height) |
|||
|
|||
if err != nil { |
|||
log.WithFields(logrus.Fields{ |
|||
"height": height, |
|||
"error": err, |
|||
}).Warn("error with getblock") |
|||
|
|||
timeoutCount++ |
|||
if timeoutCount == 3 { |
|||
log.WithFields(logrus.Fields{ |
|||
"timeouts": timeoutCount, |
|||
}).Warn("unable to issue RPC call to hushd node 3 times") |
|||
break |
|||
} |
|||
} |
|||
|
|||
if block != nil { |
|||
if timeoutCount > 0 { |
|||
timeoutCount-- |
|||
} |
|||
|
|||
log.Info("Ingestor adding block to cache: ", height) |
|||
err, reorg := cache.Add(height, block) |
|||
|
|||
if err != nil { |
|||
log.Error("Error adding block to cache: ", err) |
|||
continue |
|||
} |
|||
|
|||
//check for reorgs once we have inital block hash from startup
|
|||
if reorg { |
|||
reorgCount++ |
|||
|
|||
log.WithFields(logrus.Fields{ |
|||
"height": height, |
|||
"hash": displayHash(block.Hash), |
|||
"phash": displayHash(block.PrevHash), |
|||
"reorg": reorgCount, |
|||
}).Warn("REORG") |
|||
} else { |
|||
reorgCount = 0 |
|||
|
|||
height++ |
|||
} |
|||
} else { |
|||
break |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
func GetBlock(rpcClient *rpcclient.Client, cache *BlockCache, height int) (*walletrpc.CompactBlock, error) { |
|||
// First, check the cache to see if we have the block
|
|||
block := cache.Get(height) |
|||
if block != nil { |
|||
return block, nil |
|||
} |
|||
|
|||
// If a block was not found, make sure user is requesting a historical block
|
|||
if height > cache.GetLatestBlock() { |
|||
return nil, errors.New( |
|||
fmt.Sprintf( |
|||
"Block requested is newer than latest block. Requested: %d Latest: %d", |
|||
height, cache.GetLatestBlock())) |
|||
} |
|||
|
|||
block, err := getBlockFromRPC(rpcClient, height) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return block, nil |
|||
} |
|||
|
|||
func GetBlockRange(rpcClient *rpcclient.Client, cache *BlockCache, |
|||
blockOut chan<- walletrpc.CompactBlock, errOut chan<- error, start, end int) { |
|||
|
|||
// Go over [start, end] inclusive
|
|||
for i := start; i <= end; i++ { |
|||
block, err := GetBlock(rpcClient, cache, i) |
|||
if err != nil { |
|||
errOut <- err |
|||
return |
|||
} |
|||
|
|||
blockOut <- *block |
|||
} |
|||
|
|||
errOut <- nil |
|||
} |
|||
|
|||
func displayHash(hash []byte) string { |
|||
rhash := make([]byte, len(hash)) |
|||
copy(rhash, hash) |
|||
// Reverse byte order
|
|||
for i := 0; i < len(rhash)/2; i++ { |
|||
j := len(rhash) - 1 - i |
|||
rhash[i], rhash[j] = rhash[j], rhash[i] |
|||
} |
|||
|
|||
return hex.EncodeToString(rhash) |
|||
} |
@ -0,0 +1,6 @@ |
|||
Files: * |
|||
Copyright: 2019-2021, The Hush developers |
|||
2018-2019, The Zcash developers |
|||
License: GPLv3 |
|||
Comment: https://hush.is/developers |
|||
|
@ -0,0 +1,67 @@ |
|||
.TH LIGHTWALLET "29" "October 2021" "hightlited v0.0.1" "User Commands" |
|||
.SH NAME |
|||
lightwalletd \- manual page for hush highlited v0.0.1 |
|||
.SH DESCRIPTION |
|||
.B lightwalletd |
|||
runs a lightwallet daemon for a Hush Silent Dragon Lite node while puffing... |
|||
.PP |
|||
In order to ensure you are adequately protecting your privacy when using Hush, |
|||
please see <https://hush.is/security/>. |
|||
.SS "Usage:" |
|||
.TP |
|||
.B highlited [options] |
|||
Start Hush highlited |
|||
.TP |
|||
highlited --help |
|||
List available command line options |
|||
.TP |
|||
highlited --version |
|||
Display version information |
|||
.SH OPTIONS |
|||
.HP |
|||
\fB\-help | -h | -? |
|||
.IP |
|||
Display command line options |
|||
.HP |
|||
\fB\-conf-file\fR [conf_file location] |
|||
.IP |
|||
Configures your HUSH3.conf file location [Required to run]. Typically ~/.hush/HUSH3/HUSH3.conf |
|||
.HP |
|||
\fB\-bind-addr \fRhost.net:chosen_port |
|||
.IP |
|||
Set host.net to either a FQDN or 127.0.0.1 depending on your configuration [Required to run]. Most common port is 9067 unless changed. |
|||
.HP |
|||
\fB\-no-tls |
|||
.IP |
|||
Disable TLS, serve un-encrypted traffic. Toggle depending on your configuration. |
|||
.HP |
|||
\fB\-cache-size \fRint |
|||
.IP |
|||
Set number of blocks to hold in the cache (default 40000) |
|||
.HP |
|||
\fB\-log-file \fRstring |
|||
.IP |
|||
Set log file to write to |
|||
.HP |
|||
\fB\-log-level \fRuint |
|||
.IP |
|||
log level (logrus 1-7) (default 4) |
|||
.HP |
|||
\fB\-tls-cert \fRstring |
|||
.IP |
|||
the path to a TLS certificate (optional) |
|||
.HP |
|||
\fB\-tls-key \fRstring |
|||
.IP |
|||
the path to a TLS key file (optional) |
|||
|
|||
.SH COPYRIGHT |
|||
In order to ensure you are adequately protecting your privacy when using Hush, |
|||
please see <https://hush.is/security/>. |
|||
|
|||
Copyright (C) 2021 Jahway603 and The Hush Developers |
|||
|
|||
This is experimental Free Software! Fuck Yeah!!!!! |
|||
|
|||
Distributed under the GPLv3 software license, see the accompanying file COPYING |
|||
or <https://www.gnu.org/licenses/gpl-3.0.en.html>. |
@ -0,0 +1,39 @@ |
|||
package frontend |
|||
|
|||
// copied exactly from hush lightwalletd with this comment as only change
|
|||
|
|||
import ( |
|||
"net" |
|||
|
|||
"github.com/btcsuite/btcd/rpcclient" |
|||
"github.com/pkg/errors" |
|||
ini "gopkg.in/ini.v1" |
|||
) |
|||
|
|||
func NewZRPCFromConf(confPath string) (*rpcclient.Client, error) { |
|||
cfg, err := ini.Load(confPath) |
|||
if err != nil { |
|||
return nil, errors.Wrap(err, "failed to read config file") |
|||
} |
|||
|
|||
rpcaddr := cfg.Section("").Key("rpcbind").String() |
|||
rpcport := cfg.Section("").Key("rpcport").String() |
|||
username := cfg.Section("").Key("rpcuser").String() |
|||
password := cfg.Section("").Key("rpcpassword").String() |
|||
|
|||
return NewZRPCFromCreds(net.JoinHostPort(rpcaddr, rpcport), username, password) |
|||
} |
|||
|
|||
func NewZRPCFromCreds(addr, username, password string) (*rpcclient.Client, error) { |
|||
// Connect to local hush 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
|
|||
} |
|||
// Notice the notification parameter is nil since notifications are
|
|||
// not supported in HTTP POST mode.
|
|||
return rpcclient.New(connCfg, nil) |
|||
} |
@ -0,0 +1,333 @@ |
|||
package frontend |
|||
|
|||
import ( |
|||
"context" |
|||
"encoding/hex" |
|||
"encoding/json" |
|||
"errors" |
|||
"regexp" |
|||
"strconv" |
|||
"strings" |
|||
"time" |
|||
|
|||
"github.com/btcsuite/btcd/rpcclient" |
|||
"github.com/sirupsen/logrus" |
|||
|
|||
"git.hush.is/hush/lightwalletd/walletrpc" |
|||
"git.hush.is/jahway603/highlited/common" |
|||
) |
|||
|
|||
var ( |
|||
ErrUnspecified = errors.New("request for unspecified identifier") |
|||
) |
|||
|
|||
// the service type
|
|||
type SqlStreamer struct { |
|||
cache *common.BlockCache |
|||
client *rpcclient.Client |
|||
log *logrus.Entry |
|||
} |
|||
|
|||
func NewSQLiteStreamer(client *rpcclient.Client, cache *common.BlockCache, log *logrus.Entry) (walletrpc.CompactTxStreamerServer, error) { |
|||
return &SqlStreamer{cache, client, log}, nil |
|||
} |
|||
|
|||
func (s *SqlStreamer) GracefulStop() error { |
|||
return nil |
|||
} |
|||
|
|||
func (s *SqlStreamer) GetCache() *common.BlockCache { |
|||
return s.cache |
|||
} |
|||
|
|||
func (s *SqlStreamer) GetLatestBlock(ctx context.Context, placeholder *walletrpc.ChainSpec) (*walletrpc.BlockID, error) { |
|||
latestBlock := s.cache.GetLatestBlock() |
|||
|
|||
if latestBlock == -1 { |
|||
return nil, errors.New("Cache is empty. Server is probably not yet ready.") |
|||
} |
|||
|
|||
// TODO: also return block hashes here
|
|||
return &walletrpc.BlockID{Height: uint64(latestBlock)}, nil |
|||
} |
|||
|
|||
func (s *SqlStreamer) GetAddressTxids(addressBlockFilter *walletrpc.TransparentAddressBlockFilter, resp walletrpc.CompactTxStreamer_GetAddressTxidsServer) error { |
|||
var err error |
|||
var errCode int64 |
|||
|
|||
// Test to make sure Address is a single t address
|
|||
match, err := regexp.Match("^R[a-zA-Z0-9]{33}$", []byte(addressBlockFilter.Address)) |
|||
if err != nil || !match { |
|||
s.log.Errorf("Unrecognized address: %s", addressBlockFilter.Address) |
|||
return nil |
|||
} |
|||
|
|||
params := make([]json.RawMessage, 1) |
|||
st := "{\"addresses\": [\"" + addressBlockFilter.Address + "\"]," + |
|||
"\"start\": " + strconv.FormatUint(addressBlockFilter.Range.Start.Height, 10) + |
|||
", \"end\": " + strconv.FormatUint(addressBlockFilter.Range.End.Height, 10) + "}" |
|||
|
|||
params[0] = json.RawMessage(st) |
|||
|
|||
result, rpcErr := s.client.RawRequest("getaddresstxids", params) |
|||
|
|||
// For some reason, the error responses are not JSON
|
|||
if rpcErr != nil { |
|||
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
|
|||
if err == nil && errCode == -8 { |
|||
return nil |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
var txids []string |
|||
err = json.Unmarshal(result, &txids) |
|||
if err != nil { |
|||
s.log.Errorf("Got error: %s", err.Error()) |
|||
return nil |
|||
} |
|||
|
|||
timeout, cancel := context.WithTimeout(resp.Context(), 30*time.Second) |
|||
defer cancel() |
|||
|
|||
for _, txidstr := range txids { |
|||
txid, _ := hex.DecodeString(txidstr) |
|||
// Txid is read as a string, which is in big-endian order. But when converting
|
|||
// to bytes, it should be little-endian
|
|||
for left, right := 0, len(txid)-1; left < right; left, right = left+1, right-1 { |
|||
txid[left], txid[right] = txid[right], txid[left] |
|||
} |
|||
|
|||
tx, err := s.GetTransaction(timeout, &walletrpc.TxFilter{Hash: txid}) |
|||
if err != nil { |
|||
s.log.Errorf("Got error: %s", err.Error()) |
|||
return nil |
|||
} |
|||
|
|||
resp.Send(tx) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func (s *SqlStreamer) GetBlock(ctx context.Context, id *walletrpc.BlockID) (*walletrpc.CompactBlock, error) { |
|||
if id.Height == 0 && id.Hash == nil { |
|||
return nil, ErrUnspecified |
|||
} |
|||
|
|||
// Precedence: a hash is more specific than a height. If we have it, use it first.
|
|||
if id.Hash != nil { |
|||
// TODO: Get block by hash
|
|||
|
|||
return nil, errors.New("GetBlock by Hash is not yet implemented") |
|||
} else { |
|||
cBlock, err := common.GetBlock(s.client, s.cache, int(id.Height)) |
|||
|
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return cBlock, err |
|||
} |
|||
|
|||
} |
|||
|
|||
func (s *SqlStreamer) GetBlockRange(span *walletrpc.BlockRange, resp walletrpc.CompactTxStreamer_GetBlockRangeServer) error { |
|||
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)) |
|||
|
|||
for { |
|||
select { |
|||
case err := <-errChan: |
|||
// this will also catch context.DeadlineExceeded from the timeout
|
|||
return err |
|||
case cBlock := <-blockChan: |
|||
err := resp.Send(&cBlock) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
} |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func (s *SqlStreamer) GetTransaction(ctx context.Context, txf *walletrpc.TxFilter) (*walletrpc.RawTransaction, error) { |
|||
var txBytes []byte |
|||
var txHeight float64 |
|||
|
|||
if txf.Hash != nil { |
|||
txid := txf.Hash |
|||
for left, right := 0, len(txid)-1; left < right; left, right = left+1, right-1 { |
|||
txid[left], txid[right] = txid[right], txid[left] |
|||
} |
|||
leHashString := hex.EncodeToString(txid) |
|||
|
|||
// First call to get the raw transaction bytes
|
|||
params := make([]json.RawMessage, 1) |
|||
params[0] = json.RawMessage("\"" + leHashString + "\"") |
|||
|
|||
result, rpcErr := s.client.RawRequest("getrawtransaction", params) |
|||
|
|||
var err error |
|||
var errCode int64 |
|||
// For some reason, the error responses are not JSON
|
|||
if rpcErr != nil { |
|||
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
|
|||
if err == nil && errCode == -8 { |
|||
return nil, err |
|||
} |
|||
return nil, err |
|||
} |
|||
|
|||
var txhex string |
|||
err = json.Unmarshal(result, &txhex) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
txBytes, err = hex.DecodeString(txhex) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
// Second call to get height
|
|||
params = make([]json.RawMessage, 2) |
|||
params[0] = json.RawMessage("\"" + leHashString + "\"") |
|||
params[1] = json.RawMessage("1") |
|||
|
|||
result, rpcErr = s.client.RawRequest("getrawtransaction", params) |
|||
|
|||
// For some reason, the error responses are not JSON
|
|||
if rpcErr != nil { |
|||
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
|
|||
if err == nil && errCode == -8 { |
|||
return nil, err |
|||
} |
|||
return nil, err |
|||
} |
|||
var txinfo interface{} |
|||
err = json.Unmarshal(result, &txinfo) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
txHeight = txinfo.(map[string]interface{})["height"].(float64) |
|||
|
|||
return &walletrpc.RawTransaction{Data: txBytes, Height: uint64(txHeight)}, nil |
|||
} |
|||
|
|||
if txf.Block.Hash != nil { |
|||
s.log.Error("Can't GetTransaction with a blockhash+num. Please call GetTransaction with txid") |
|||
return nil, errors.New("Can't GetTransaction with a blockhash+num. Please call GetTransaction with txid") |
|||
} |
|||
|
|||
return &walletrpc.RawTransaction{Data: txBytes, Height: uint64(txHeight)}, nil |
|||
} |
|||
|
|||
// GetLightdInfo gets the LightWalletD (this server) info
|
|||
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) |
|||
|
|||
if err != nil { |
|||
s.log.WithFields(logrus.Fields{ |
|||
"error": err, |
|||
}).Warn("Unable to get sapling activation height") |
|||
return nil, err |
|||
} |
|||
|
|||
// 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.0.1-highlited", |
|||
Vendor: "Silentdragonlite HighLiteD", |
|||
TaddrSupport: true, |
|||
ChainName: chainName, |
|||
SaplingActivationHeight: uint64(saplingHeight), |
|||
ConsensusBranchId: consensusBranchId, |
|||
BlockHeight: uint64(blockHeight), |
|||
Difficulty: uint64(difficulty), |
|||
Longestchain: uint64(longestchain), |
|||
Notarized: uint64(notarized), |
|||
}, nil |
|||
} |
|||
|
|||
// GetCoinsupply gets the Coinsupply info
|
|||
func (s *SqlStreamer) GetCoinsupply(ctx context.Context, in *walletrpc.Empty) (*walletrpc.Coinsupply, error) { |
|||
result, coin, height, supply, zfunds, total, err := common.GetCoinsupply(s.client) |
|||
|
|||
if err != nil { |
|||
s.log.WithFields(logrus.Fields{ |
|||
"error": err, |
|||
}).Warn("Unable to get Coinsupply") |
|||
return nil, err |
|||
} |
|||
|
|||
// TODO these are called Error but they aren't at the moment.
|
|||
// A success will return code 0 and message txhash.
|
|||
return &walletrpc.Coinsupply{ |
|||
Result: result, |
|||
Coin: coin, |
|||
Height: uint64(height), |
|||
Supply: uint64(supply), |
|||
Zfunds: uint64(zfunds), |
|||
Total: uint64(total), |
|||
}, nil |
|||
} |
|||
|
|||
// SendTransaction forwards raw transaction bytes to a hushd instance over JSON-RPC
|
|||
func (s *SqlStreamer) SendTransaction(ctx context.Context, rawtx *walletrpc.RawTransaction) (*walletrpc.SendResponse, error) { |
|||
// sendrawtransaction "hexstring" ( allowhighfees )
|
|||
//
|
|||
// Submits raw transaction (serialized, hex-encoded) to local node and network.
|
|||
//
|
|||
// Also see createrawtransaction and signrawtransaction calls.
|
|||
//
|
|||
// Arguments:
|
|||
// 1. "hexstring" (string, required) The hex string of the raw transaction)
|
|||
// 2. allowhighfees (boolean, optional, default=false) Allow high fees
|
|||
//
|
|||
// Result:
|
|||
// "hex" (string) The transaction hash in hex
|
|||
|
|||
// Construct raw JSON-RPC params
|
|||
params := make([]json.RawMessage, 1) |
|||
txHexString := hex.EncodeToString(rawtx.Data) |
|||
params[0] = json.RawMessage("\"" + txHexString + "\"") |
|||
result, rpcErr := s.client.RawRequest("sendrawtransaction", params) |
|||
|
|||
var err error |
|||
var errCode int64 |
|||
var errMsg string |
|||
|
|||
// For some reason, the error responses are not JSON
|
|||
if rpcErr != nil { |
|||
errParts := strings.SplitN(rpcErr.Error(), ":", 2) |
|||
errMsg = strings.TrimSpace(errParts[1]) |
|||
errCode, err = strconv.ParseInt(errParts[0], 10, 32) |
|||
if err != nil { |
|||
// This should never happen. We can't panic here, but it's that class of error.
|
|||
// This is why we need integration testing to work better than regtest currently does. TODO.
|
|||
return nil, errors.New("SendTransaction couldn't parse error code") |
|||
} |
|||
} else { |
|||
errMsg = string(result) |
|||
} |
|||
|
|||
// TODO these are called Error but they aren't at the moment.
|
|||
// A success will return code 0 and message txhash.
|
|||
return &walletrpc.SendResponse{ |
|||
ErrorCode: int32(errCode), |
|||
ErrorMessage: errMsg, |
|||
}, nil |
|||
} |
Loading…
Reference in new issue