Merge branch 'master' into ethereum
This commit is contained in:
commit
73486e851f
@ -119,6 +119,11 @@ func (c *blockChainWithMetrics) EstimateSmartFee(blocks int, conservative bool)
|
||||
return c.b.EstimateSmartFee(blocks, conservative)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) EstimateFee(blocks int) (v float64, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("EstimateFee", s, err) }(time.Now())
|
||||
return c.b.EstimateFee(blocks)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) SendRawTransaction(tx string) (v string, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("SendRawTransaction", s, err) }(time.Now())
|
||||
return c.b.SendRawTransaction(tx)
|
||||
|
||||
@ -33,10 +33,12 @@ func GetChainParams(chain string) *chaincfg.Params {
|
||||
return &chaincfg.MainNetParams
|
||||
}
|
||||
|
||||
// GetAddrIDFromAddress returns internal address representation of given transaction output
|
||||
func (p *BitcoinBlockParser) GetAddrIDFromVout(output *bchain.Vout) ([]byte, error) {
|
||||
return hex.DecodeString(output.ScriptPubKey.Hex)
|
||||
}
|
||||
|
||||
// GetAddrIDFromAddress returns internal address representation of given address
|
||||
func (p *BitcoinBlockParser) GetAddrIDFromAddress(address string) ([]byte, error) {
|
||||
return p.AddressToOutputScript(address)
|
||||
}
|
||||
|
||||
@ -255,6 +255,20 @@ type resEstimateSmartFee struct {
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// estimatefee
|
||||
|
||||
type cmdEstimateFee struct {
|
||||
Method string `json:"method"`
|
||||
Params struct {
|
||||
Blocks int `json:"nblocks"`
|
||||
} `json:"params"`
|
||||
}
|
||||
|
||||
type resEstimateFee struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result float64 `json:"result"`
|
||||
}
|
||||
|
||||
// sendrawtransaction
|
||||
|
||||
type cmdSendRawTransaction struct {
|
||||
@ -582,6 +596,24 @@ func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (float64, e
|
||||
return res.Result.Feerate, nil
|
||||
}
|
||||
|
||||
// EstimateFee returns fee estimation.
|
||||
func (b *BitcoinRPC) EstimateFee(blocks int) (float64, error) {
|
||||
glog.V(1).Info("rpc: estimatefee ", blocks)
|
||||
|
||||
res := resEstimateFee{}
|
||||
req := cmdEstimateFee{Method: "estimatefee"}
|
||||
req.Params.Blocks = blocks
|
||||
err := b.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if res.Error != nil {
|
||||
return 0, res.Error
|
||||
}
|
||||
return res.Result, nil
|
||||
}
|
||||
|
||||
// SendRawTransaction sends raw transaction.
|
||||
func (b *BitcoinRPC) SendRawTransaction(tx string) (string, error) {
|
||||
glog.V(1).Info("rpc: sendrawtransaction")
|
||||
@ -600,6 +632,7 @@ func (b *BitcoinRPC) SendRawTransaction(tx string) (string, error) {
|
||||
return res.Result, nil
|
||||
}
|
||||
|
||||
// GetMempoolEntry returns mempool data for given transaction
|
||||
func (b *BitcoinRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
|
||||
glog.V(1).Info("rpc: getmempoolentry")
|
||||
|
||||
|
||||
41
bchain/coins/zec/address.go
Normal file
41
bchain/coins/zec/address.go
Normal file
@ -0,0 +1,41 @@
|
||||
package zec
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrChecksumMismatch describes an error where decoding failed due
|
||||
// to a bad checksum.
|
||||
ErrChecksumMismatch = errors.New("checksum mismatch")
|
||||
|
||||
// ErrInvalidFormat describes an error where decoding failed due to invalid version
|
||||
ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing")
|
||||
)
|
||||
|
||||
// checksum: first four bytes of sha256^2
|
||||
func checksum(input []byte) (cksum [4]byte) {
|
||||
h := sha256.Sum256(input)
|
||||
h2 := sha256.Sum256(h[:])
|
||||
copy(cksum[:], h2[:4])
|
||||
return
|
||||
}
|
||||
|
||||
// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum.
|
||||
func CheckDecode(input string) (result []byte, version []byte, err error) {
|
||||
decoded := base58.Decode(input)
|
||||
if len(decoded) < 5 {
|
||||
return nil, nil, ErrInvalidFormat
|
||||
}
|
||||
version = append(version, decoded[0:2]...)
|
||||
var cksum [4]byte
|
||||
copy(cksum[:], decoded[len(decoded)-4:])
|
||||
if checksum(decoded[:len(decoded)-4]) != cksum {
|
||||
return nil, nil, ErrChecksumMismatch
|
||||
}
|
||||
payload := decoded[2 : len(decoded)-4]
|
||||
result = append(result, payload...)
|
||||
return
|
||||
}
|
||||
@ -2,37 +2,83 @@ package zec
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins/btc"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// bitcoinwire parsing
|
||||
|
||||
type ZCashBlockParser struct {
|
||||
btc.BitcoinBlockParser
|
||||
}
|
||||
|
||||
// getChainParams contains network parameters for the main Bitcoin network,
|
||||
// the regression test Bitcoin network, the test Bitcoin network and
|
||||
// the simulation test Bitcoin network, in this order
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
switch chain {
|
||||
case "test":
|
||||
return &chaincfg.TestNet3Params
|
||||
case "regtest":
|
||||
return &chaincfg.RegressionNetParams
|
||||
}
|
||||
return &chaincfg.MainNetParams
|
||||
}
|
||||
type ZCashBlockParser struct{}
|
||||
|
||||
// GetAddrIDFromAddress returns internal address representation of given transaction output
|
||||
func (p *ZCashBlockParser) GetAddrIDFromVout(output *bchain.Vout) ([]byte, error) {
|
||||
if len(output.ScriptPubKey.Addresses) != 1 {
|
||||
return nil, nil
|
||||
}
|
||||
return []byte(output.ScriptPubKey.Addresses[0]), nil
|
||||
hash, _, err := CheckDecode(output.ScriptPubKey.Addresses[0])
|
||||
return hash, err
|
||||
}
|
||||
|
||||
// GetAddrIDFromAddress returns internal address representation of given address
|
||||
func (p *ZCashBlockParser) GetAddrIDFromAddress(address string) ([]byte, error) {
|
||||
return []byte(address), nil
|
||||
hash, _, err := CheckDecode(address)
|
||||
return hash, err
|
||||
}
|
||||
|
||||
// PackTx packs transaction to byte array
|
||||
func (p *ZCashBlockParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||
buf := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(buf, height)
|
||||
buf, err := encodeTx(buf, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func encodeTx(b []byte, tx *bchain.Tx) ([]byte, error) {
|
||||
buf := bytes.NewBuffer(b)
|
||||
enc := gob.NewEncoder(buf)
|
||||
err := enc.Encode(tx)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// UnpackTx unpacks transaction from byte array
|
||||
func (p *ZCashBlockParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
height := binary.BigEndian.Uint32(buf)
|
||||
tx, err := decodeTx(buf[4:])
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return tx, height, nil
|
||||
}
|
||||
|
||||
func decodeTx(buf []byte) (*bchain.Tx, error) {
|
||||
tx := new(bchain.Tx)
|
||||
dec := gob.NewDecoder(bytes.NewBuffer(buf))
|
||||
err := dec.Decode(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func (p *ZCashBlockParser) AddressToOutputScript(address string) ([]byte, error) {
|
||||
return nil, errors.New("AddressToOutputScript: not implemented")
|
||||
}
|
||||
|
||||
func (p *ZCashBlockParser) OutputScriptToAddresses(script []byte) ([]string, error) {
|
||||
return nil, errors.New("OutputScriptToAddresses: not implemented")
|
||||
}
|
||||
|
||||
func (p *ZCashBlockParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
||||
return nil, errors.New("ParseBlock: not implemented")
|
||||
}
|
||||
|
||||
func (p *ZCashBlockParser) ParseTx(b []byte) (*bchain.Tx, error) {
|
||||
return nil, errors.New("ParseTx: not implemented")
|
||||
}
|
||||
|
||||
func (p *ZCashBlockParser) IsUTXOChain() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
185
bchain/coins/zec/zcashparser_test.go
Normal file
185
bchain/coins/zec/zcashparser_test.go
Normal file
File diff suppressed because one or more lines are too long
@ -5,8 +5,6 @@ import (
|
||||
"blockbook/bchain/coins/btc"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
@ -28,31 +26,11 @@ func NewZCashRPC(config json.RawMessage, pushHandler func(*bchain.MQMessage)) (b
|
||||
|
||||
func (z *ZCashRPC) Initialize() error {
|
||||
z.Mempool = bchain.NewMempool(z)
|
||||
z.Parser = &ZCashBlockParser{}
|
||||
z.Testnet = false
|
||||
z.Network = "livenet"
|
||||
|
||||
chainName, err := z.GetBlockChainInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params := GetChainParams(chainName)
|
||||
|
||||
// always create parser
|
||||
z.Parser = &ZCashBlockParser{
|
||||
btc.BitcoinBlockParser{
|
||||
Params: params,
|
||||
},
|
||||
}
|
||||
|
||||
// parameters for getInfo request
|
||||
if params.Net == wire.MainNet {
|
||||
z.Testnet = false
|
||||
z.Network = "livenet"
|
||||
} else {
|
||||
z.Testnet = true
|
||||
z.Network = "testnet"
|
||||
}
|
||||
|
||||
glog.Info("rpc: block chain ", params.Name)
|
||||
glog.Info("rpc: block chain mainnet")
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -90,15 +68,30 @@ type resGetBlockHeader struct {
|
||||
Result bchain.BlockHeader `json:"result"`
|
||||
}
|
||||
|
||||
// estimatefee
|
||||
|
||||
type resEstimateFee struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result float64 `json:"result"`
|
||||
}
|
||||
|
||||
// GetBlock returns block with given hash.
|
||||
func (z *ZCashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
var err error
|
||||
if hash == "" && height > 0 {
|
||||
hash, err = z.GetBlockHash(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(1).Info("rpc: getblock (verbosity=1) ", hash)
|
||||
|
||||
res := resGetBlockThin{}
|
||||
req := untypedArrayParams{Method: "getblock"}
|
||||
req.Params = append(req.Params, hash)
|
||||
req.Params = append(req.Params, true)
|
||||
err := z.Call(&req, &res)
|
||||
err = z.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
@ -111,6 +104,10 @@ func (z *ZCashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
for i, txid := range res.Result.Txids {
|
||||
tx, err := z.GetTransaction(txid)
|
||||
if err != nil {
|
||||
if isInvalidTx(err) {
|
||||
glog.Errorf("rpc: getblock: skipping transanction in block %s due error: %s", hash, err)
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
txs[i] = *tx
|
||||
@ -122,6 +119,20 @@ func (z *ZCashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func isInvalidTx(err error) bool {
|
||||
switch e1 := err.(type) {
|
||||
case *errors.Err:
|
||||
switch e2 := e1.Cause().(type) {
|
||||
case *bchain.RPCError:
|
||||
if e2.Code == -5 { // "No information available about transaction"
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetTransaction returns a transaction by the transaction ID.
|
||||
func (z *ZCashRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
||||
glog.V(1).Info("rpc: getrawtransaction ", txid)
|
||||
@ -154,6 +165,9 @@ func (z *ZCashRPC) GetBlockHash(height uint32) (string, error) {
|
||||
return "", errors.Annotatef(err, "height %v", height)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if isErrBlockNotFound(res.Error) {
|
||||
return "", bchain.ErrBlockNotFound
|
||||
}
|
||||
return "", errors.Annotatef(res.Error, "height %v", height)
|
||||
}
|
||||
return res.Result, nil
|
||||
@ -173,7 +187,49 @@ func (z *ZCashRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if isErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
return &res.Result, nil
|
||||
}
|
||||
|
||||
// EstimateSmartFee returns fee estimation.
|
||||
func (z *ZCashRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) {
|
||||
glog.V(1).Info("rpc: estimatesmartfee")
|
||||
|
||||
return z.estimateFee(blocks)
|
||||
}
|
||||
|
||||
// EstimateFee returns fee estimation.
|
||||
func (z *ZCashRPC) EstimateFee(blocks int) (float64, error) {
|
||||
glog.V(1).Info("rpc: estimatefee ", blocks)
|
||||
|
||||
return z.estimateFee(blocks)
|
||||
}
|
||||
|
||||
func (z *ZCashRPC) estimateFee(blocks int) (float64, error) {
|
||||
res := resEstimateFee{}
|
||||
req := untypedArrayParams{Method: "estimatefee"}
|
||||
req.Params = append(req.Params, blocks)
|
||||
err := z.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if res.Error != nil {
|
||||
return 0, res.Error
|
||||
}
|
||||
return res.Result, nil
|
||||
}
|
||||
|
||||
// GetMempoolEntry returns mempool data for given transaction
|
||||
func (z *ZCashRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
|
||||
return nil, errors.New("GetMempoolEntry: not implemented")
|
||||
}
|
||||
|
||||
func isErrBlockNotFound(err *bchain.RPCError) bool {
|
||||
return err.Message == "Block not found" ||
|
||||
err.Message == "Block height out of range"
|
||||
}
|
||||
|
||||
@ -113,6 +113,7 @@ type BlockChain interface {
|
||||
GetMempool() ([]string, error)
|
||||
GetTransaction(txid string) (*Tx, error)
|
||||
EstimateSmartFee(blocks int, conservative bool) (float64, error)
|
||||
EstimateFee(blocks int) (float64, error)
|
||||
SendRawTransaction(tx string) (string, error)
|
||||
// mempool
|
||||
ResyncMempool(onNewTxAddr func(txid string, addr string)) error
|
||||
|
||||
@ -162,6 +162,13 @@ var onMessageHandlers = map[string]func(*SocketIoServer, json.RawMessage) (inter
|
||||
}
|
||||
return
|
||||
},
|
||||
"estimateFee": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) {
|
||||
blocks, err := unmarshalEstimateFee(params)
|
||||
if err == nil {
|
||||
rv, err = s.estimateFee(blocks)
|
||||
}
|
||||
return
|
||||
},
|
||||
"getInfo": func(s *SocketIoServer, params json.RawMessage) (rv interface{}, err error) {
|
||||
return s.getInfo()
|
||||
},
|
||||
@ -527,6 +534,33 @@ func (s *SocketIoServer) estimateSmartFee(blocks int, conservative bool) (res re
|
||||
return
|
||||
}
|
||||
|
||||
func unmarshalEstimateFee(params []byte) (blocks int, err error) {
|
||||
p, err := unmarshalArray(params, 1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fblocks, ok := p[0].(float64)
|
||||
if !ok {
|
||||
err = errors.New("Invalid parameter nblocks")
|
||||
return
|
||||
}
|
||||
blocks = int(fblocks)
|
||||
return
|
||||
}
|
||||
|
||||
type resultEstimateFee struct {
|
||||
Result float64 `json:"result"`
|
||||
}
|
||||
|
||||
func (s *SocketIoServer) estimateFee(blocks int) (res resultEstimateFee, err error) {
|
||||
fee, err := s.chain.EstimateFee(blocks)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res.Result = fee
|
||||
return
|
||||
}
|
||||
|
||||
type resultGetInfo struct {
|
||||
Result struct {
|
||||
Version int `json:"version,omitempty"`
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
|
||||
function getAddressTxids() {
|
||||
var addresses = document.getElementById('getAddressTxidsAddresses').value.split(",");
|
||||
addresses = addresses.map(s => s.trim());
|
||||
var mempool = document.getElementById("getAddressTxidsMempool").checked;
|
||||
lookupTransactionsIdsMempool(addresses, mempool, 2000000, 0, function (result) {
|
||||
console.log('getAddressTxids sent successfully');
|
||||
@ -53,6 +54,7 @@
|
||||
|
||||
function getAddressHistory() {
|
||||
var addresses = document.getElementById('getAddressHistoryAddresses').value.split(",");
|
||||
addresses = addresses.map(s => s.trim());
|
||||
var mempool = document.getElementById("getAddressHistoryMempool").checked;
|
||||
lookupAddressHistories(addresses, 0, 5, mempool, 2000000, 0, function (result) {
|
||||
console.log('getAddressHistory sent successfully');
|
||||
@ -102,22 +104,27 @@
|
||||
}
|
||||
|
||||
function getBlockHeader() {
|
||||
var height = document.getElementById('getBlockHeaderHeight').value;
|
||||
lookupBlockHash(parseInt(height), function (result) {
|
||||
var param = document.getElementById('getBlockHeaderParam').value.trim();
|
||||
lookupBlockHash(isHash(param) ? param : parseInt(param), function (result) {
|
||||
console.log('getBlockHeader sent successfully');
|
||||
console.log(result);
|
||||
document.getElementById('getBlockHeaderResult').innerText = JSON.stringify(result).replace(/,/g, ", ");
|
||||
});
|
||||
}
|
||||
|
||||
function lookupBlockHash(height, f) {
|
||||
function isHash(str) {
|
||||
var re = /[0-9A-Fa-f]{64}/g;
|
||||
return re.test(str);
|
||||
}
|
||||
|
||||
function lookupBlockHash(heightOrHash, f) {
|
||||
const method = 'getBlockHeader';
|
||||
const params = [height];
|
||||
const params = [heightOrHash];
|
||||
return socket.send({ method, params }, f);
|
||||
}
|
||||
|
||||
function estimateSmartFee() {
|
||||
var blocks = document.getElementById('estimateSmartFeeBlocks').value;
|
||||
var blocks = document.getElementById('estimateSmartFeeBlocks').value.trim();
|
||||
var conservative = document.getElementById("estimateSmartFeeConservative").checked;
|
||||
estimateSmartTxFee(parseInt(blocks), conservative, function (result) {
|
||||
console.log('estimateSmartFee sent successfully');
|
||||
@ -147,7 +154,7 @@
|
||||
}
|
||||
|
||||
function getDetailedTransaction() {
|
||||
var hash = document.getElementById('getDetailedTransactionHash').value;
|
||||
var hash = document.getElementById('getDetailedTransactionHash').value.trim();
|
||||
lookupDetailedTransaction(hash, function (result) {
|
||||
console.log('getDetailedTransaction sent successfully');
|
||||
console.log(result);
|
||||
@ -164,7 +171,7 @@
|
||||
}
|
||||
|
||||
function sendTransaction() {
|
||||
var tx = document.getElementById('sendTransactionHex').value;
|
||||
var tx = document.getElementById('sendTransactionHex').value.trim();
|
||||
sendTransactionF(tx, function (result) {
|
||||
console.log('sendTransaction sent successfully');
|
||||
console.log(result);
|
||||
@ -194,6 +201,7 @@
|
||||
|
||||
function subscribeAddressTxid() {
|
||||
var addresses = document.getElementById('subscribeAddressTxidAddresses').value.split(",");
|
||||
addresses = addresses.map(s => s.trim());
|
||||
socket.emit('subscribe', "bitcoind/addresstxid", addresses, function (result) {
|
||||
console.log('subscribe bitcoind/addresstxid sent successfully');
|
||||
console.log(result);
|
||||
@ -206,7 +214,7 @@
|
||||
}
|
||||
|
||||
function getMempoolEntry() {
|
||||
var hash = document.getElementById('getMempoolEntryHash').value;
|
||||
var hash = document.getElementById('getMempoolEntryHash').value.trim();
|
||||
lookupMempoolEntry(hash, function (result) {
|
||||
console.log('getMempoolEntry sent successfully');
|
||||
console.log(result);
|
||||
@ -277,7 +285,7 @@
|
||||
<input class="btn btn-secondary" type="button" value="getBlockHeader" onclick="getBlockHeader()">
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<input type="text" class="form-control" id="getBlockHeaderHeight" value="0">
|
||||
<input type="text" class="form-control" id="getBlockHeaderParam" value="0">
|
||||
</div>
|
||||
<div class="col">
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user