Merge branch 'master' into ethereum

This commit is contained in:
Martin Boehm 2018-03-27 15:40:06 +02:00
commit 73486e851f
10 changed files with 470 additions and 59 deletions

View File

@ -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)

View File

@ -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)
}

View File

@ -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")

View 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
}

View File

@ -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
}

File diff suppressed because one or more lines are too long

View File

@ -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"
}

View File

@ -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

View File

@ -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"`

View File

@ -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>