From d09333fa2e520cac3e546b77ce2977740fe6565d Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Mon, 19 Mar 2018 15:46:29 +0100 Subject: [PATCH] WIP: zcash --- bchain/coins/blockchain.go | 8 +- bchain/coins/btc/bitcoinrpc.go | 57 ++++++-------- bchain/coins/zec/zcashrpc.go | 131 ++++++++++++++++++++++++++++++++- bchain/coins/zec/zec.md | 45 +++++++++++ bchain/types.go | 3 +- 5 files changed, 207 insertions(+), 37 deletions(-) create mode 100644 bchain/coins/zec/zec.md diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index d17f26ef..ee7b06bb 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -9,6 +9,7 @@ import ( "fmt" "io/ioutil" "reflect" + "time" "github.com/juju/errors" ) @@ -38,5 +39,10 @@ func NewBlockChain(coin string, configfile string, pushHandler func(*bchain.MQMe if err != nil { return nil, errors.Annotatef(err, "Error parsing file %v", configfile) } - return bcf(config, pushHandler, metrics) + bc, err := bcf(config, pushHandler, metrics) + if err != nil { + return nil, err + } + bc.Initialize(bchain.NewMempool(bc, metrics)) + return bc, nil } diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index cc4e6c9b..d57c94d2 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -92,8 +92,6 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(*bchain.MQMessage), } s.mq = mq - s.Mempool = bchain.NewMempool(s, metrics) - return s, nil } @@ -107,6 +105,10 @@ func (b *BitcoinRPC) Shutdown() error { return nil } +func (b *BitcoinRPC) Initialize(mempool *bchain.Mempool) { + b.Mempool = mempool +} + func (b *BitcoinRPC) IsTestnet() bool { return b.Testnet } @@ -188,12 +190,7 @@ type cmdGetBlockHeader struct { } `json:"params"` } -type resGetBlockHeaderRaw struct { - Error *bchain.RPCError `json:"error"` - Result string `json:"result"` -} - -type resGetBlockHeaderVerbose struct { +type resGetBlockHeader struct { Error *bchain.RPCError `json:"error"` Result bchain.BlockHeader `json:"result"` } @@ -233,12 +230,7 @@ type cmdGetRawTransaction struct { } `json:"params"` } -type resGetRawTransactionRaw struct { - Error *bchain.RPCError `json:"error"` - Result string `json:"result"` -} - -type resGetRawTransactionVerbose struct { +type resGetRawTransaction struct { Error *bchain.RPCError `json:"error"` Result bchain.Tx `json:"result"` } @@ -292,7 +284,7 @@ func (b *BitcoinRPC) GetBestBlockHash() (string, error) { res := resGetBestBlockHash{} req := cmdGetBestBlockHash{Method: "getbestblockhash"} - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return "", err @@ -309,7 +301,7 @@ func (b *BitcoinRPC) GetBestBlockHeight() (uint32, error) { res := resGetBlockCount{} req := cmdGetBlockCount{Method: "getblockcount"} - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return 0, err @@ -326,7 +318,7 @@ func (b *BitcoinRPC) GetBlockChainInfo() (string, error) { res := resGetBlockChainInfo{} req := cmdGetBlockChainInfo{Method: "getblockchaininfo"} - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return "", err @@ -344,7 +336,7 @@ func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) { res := resGetBlockHash{} req := cmdGetBlockHash{Method: "getblockhash"} req.Params.Height = height - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return "", errors.Annotatef(err, "height %v", height) @@ -359,11 +351,11 @@ func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) { func (b *BitcoinRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { glog.V(1).Info("rpc: getblockheader") - res := resGetBlockHeaderVerbose{} + res := resGetBlockHeader{} req := cmdGetBlockHeader{Method: "getblockheader"} req.Params.BlockHash = hash req.Params.Verbose = true - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return nil, errors.Annotatef(err, "hash %v", hash) @@ -402,9 +394,6 @@ func (b *BitcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) // getBlockWithoutHeader is an optimization - it does not call GetBlockHeader to get prev, next hashes // instead it sets to header only block hash and height passed in parameters func (b *BitcoinRPC) getBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) { - if !b.ParseBlocks { - return b.GetBlockFull(hash) - } data, err := b.GetBlockRaw(hash) if err != nil { return nil, err @@ -426,7 +415,7 @@ func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) { req := cmdGetBlock{Method: "getblock"} req.Params.BlockHash = hash req.Params.Verbosity = 0 - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return nil, errors.Annotatef(err, "hash %v", hash) @@ -446,7 +435,7 @@ func (b *BitcoinRPC) GetBlockList(hash string) (*bchain.Block, error) { req := cmdGetBlock{Method: "getblock"} req.Params.BlockHash = hash req.Params.Verbosity = 1 - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return nil, errors.Annotatef(err, "hash %v", hash) @@ -478,7 +467,7 @@ func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) { req := cmdGetBlock{Method: "getblock"} req.Params.BlockHash = hash req.Params.Verbosity = 2 - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return nil, errors.Annotatef(err, "hash %v", hash) @@ -495,7 +484,7 @@ func (b *BitcoinRPC) GetMempool() ([]string, error) { res := resGetMempool{} req := cmdGetMempool{Method: "getrawmempool"} - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return nil, err @@ -510,11 +499,11 @@ func (b *BitcoinRPC) GetMempool() ([]string, error) { func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) { glog.V(1).Info("rpc: getrawtransaction ", txid) - res := resGetRawTransactionVerbose{} + res := resGetRawTransaction{} req := cmdGetRawTransaction{Method: "getrawtransaction"} req.Params.Txid = txid req.Params.Verbose = true - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return nil, errors.Annotatef(err, "txid %v", txid) @@ -553,7 +542,7 @@ func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (float64, e } else { req.Params.EstimateMode = "ECONOMICAL" } - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return 0, err @@ -571,7 +560,7 @@ func (b *BitcoinRPC) SendRawTransaction(tx string) (string, error) { res := resSendRawTransaction{} req := cmdSendRawTransaction{Method: "sendrawtransaction"} req.Params = []string{tx} - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return "", err @@ -590,7 +579,7 @@ func (b *BitcoinRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) Method: "getmempoolentry", Params: []string{txid}, } - err := b.observeRPCLatency(req.Method, func() error { return b.call(&req, &res) }) + err := b.Call(req.Method, &req, &res) if err != nil { return nil, err @@ -601,9 +590,9 @@ func (b *BitcoinRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) return res.Result, nil } -func (b *BitcoinRPC) observeRPCLatency(method string, fn func() error) error { +func (b *BitcoinRPC) Call(method string, req interface{}, res interface{}) error { start := time.Now() - err := fn() + err := b.call(req, res) if err == nil { b.metrics.RPCLatency.With(common.Labels{"method": method}).Observe(float64(time.Since(start)) / 1e6) // in milliseconds } diff --git a/bchain/coins/zec/zcashrpc.go b/bchain/coins/zec/zcashrpc.go index 4f7e38db..90ff6c82 100644 --- a/bchain/coins/zec/zcashrpc.go +++ b/bchain/coins/zec/zcashrpc.go @@ -4,7 +4,10 @@ import ( "blockbook/bchain" "blockbook/bchain/coins/btc" "blockbook/common" - "encoding/json" + "time" + + "github.com/golang/glog" + "github.com/juju/errors" ) type ZCashRPC struct { @@ -21,3 +24,129 @@ func NewZCashRPC(config json.RawMessage, pushHandler func(*bchain.MQMessage), me } return z, nil } + +type untypedArrayParams struct { + Method string `json:"method"` + Params []interface{} `json:"params"` +} + +// getblockhash + +type resGetBlockHash struct { + Error *bchain.RPCError `json:"error"` + Result string `json:"result"` +} + +// getblock + +type resGetBlockThin struct { + Error *bchain.RPCError `json:"error"` + Result bchain.ThinBlock `json:"result"` +} + +// getrawtransaction + +type resGetRawTransaction struct { + Error *bchain.RPCError `json:"error"` + Result bchain.Tx `json:"result"` +} + +// getblockheader + +type resGetBlockHeader struct { + Error *bchain.RPCError `json:"error"` + Result bchain.BlockHeader `json:"result"` +} + +// GetBlock returns block with given hash. +func (z *ZCashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { + 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.Method, &req, &res) + + if err != nil { + return nil, errors.Annotatef(err, "hash %v", hash) + } + if res.Error != nil { + return nil, errors.Annotatef(res.Error, "hash %v", hash) + } + + txs := make([]bchain.Tx, len(res.Result.Txids)) + for i, txid := range res.Result.Txids { + tx, err := z.GetTransaction(txid) + if err != nil { + return nil, err + } + txs[i] = *tx + } + block := &bchain.Block{ + BlockHeader: res.Result.BlockHeader, + Txs: txs, + } + return block, nil +} + +// GetTransaction returns a transaction by the transaction ID. +func (z *ZCashRPC) GetTransaction(txid string) (*bchain.Tx, error) { + glog.V(1).Info("rpc: getrawtransaction ", txid) + + res := resGetRawTransaction{} + req := untypedArrayParams{Method: "getrawtransaction"} + req.Params = append(req.Params, txid) + req.Params = append(req.Params, 1) + err := z.Call(req.Method, &req, &res) + + if err != nil { + return nil, errors.Annotatef(err, "txid %v", txid) + } + if res.Error != nil { + return nil, errors.Annotatef(res.Error, "txid %v", txid) + } + return &res.Result, nil +} + +// GetBlockHash returns hash of block in best-block-chain at given height. +func (z *ZCashRPC) GetBlockHash(height uint32) (string, error) { + glog.V(1).Info("rpc: getblockhash ", height) + + res := resGetBlockHash{} + req := untypedArrayParams{Method: "getblockhash"} + req.Params = append(req.Params, height) + err := z.Call(req.Method, &req, &res) + + if err != nil { + return "", errors.Annotatef(err, "height %v", height) + } + if res.Error != nil { + return "", errors.Annotatef(res.Error, "height %v", height) + } + return res.Result, nil +} + +// GetBlockHeader returns header of block with given hash. +func (z *ZCashRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { + glog.V(1).Info("rpc: getblockheader") + + res := resGetBlockHeader{} + req := untypedArrayParams{Method: "getblockheader"} + req.Params = append(req.Params, hash) + req.Params = append(req.Params, true) + err := z.Call(req.Method, &req, &res) + + if err != nil { + return nil, errors.Annotatef(err, "hash %v", hash) + } + if res.Error != nil { + return nil, errors.Annotatef(res.Error, "hash %v", hash) + } + return &res.Result, nil +} + +// GetChainParser returns BlockChainParser +func (z *ZCashRPC) GetChainParser() bchain.BlockChainParser { + return z.Parser +} diff --git a/bchain/coins/zec/zec.md b/bchain/coins/zec/zec.md new file mode 100644 index 00000000..f3adeecb --- /dev/null +++ b/bchain/coins/zec/zec.md @@ -0,0 +1,45 @@ +## Zcash Setup +Get Zcash client +``` +wget https://z.cash/downloads/zcash-1.0.15-linux64.tar.gz +tar xzf zcash-1.0.15-linux64.tar.gz +``` + +Run command to download the parameters used to create and verify shielded transactions: +``` +zcash-1.0.15/bin/zcash-fetch-params +``` + +Data are stored in */data/zec* , in folders */data/zec/zcash* for Zcash client data, */data/zec/blockbook* for Blockbook data. + +Create configuration file */data/zec/zcash/zcash.conf* with content +``` +daemon=1 +server=1 +rpcuser=rpc +rpcpassword=rpc +rpcport=8232 +txindex=1 +mainnet=1 +addnode=mainnet.z.cash +``` + +Create script *run-zec-zcashd.sh* that starts the zcashd daemon with increased rpcworkqueue and configured zeromq +``` +#!/bin/bash + +zcash-1.0.15/bin/zcashd -datadir=/data/zec/zcash -rpcworkqueue=32 -zmqpubhashblock=tcp://127.0.0.1:8234 -zmqpubrawblock=tcp://127.0.0.1:8234 -zmqpubhashtx=tcp://127.0.0.1:8234 -zmqpubrawtx=tcp://127.0.0.1:8234 +``` + +Run the *run-zec-zcashd.sh* to get initial import of data. + +Create *run-zec-blockbook.sh* script that starts blockbook +``` +#!/bin/bash +./blockbook -path=/data/zec/blockbook/db -sync -parse -rpcurl=http://127.0.0.1:8232 -httpserver=:8235 -socketio=:8236 -certfile=server/testcert -zeromq=tcp://127.0.0.1:8234 -explorer=https://zec-bitcore1.trezor.io -coin=zec $1 +``` + +To run blockbook with logging to file (run with nohup or daemonize using screen) +``` +./run-zec-blockbook.sh 2> /data/zec/blockbook/blockbook.log +``` diff --git a/bchain/types.go b/bchain/types.go index 8d982021..4d0d5e28 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -86,7 +86,8 @@ func (e *RPCError) Error() string { } type BlockChain interface { - // cleanup + // life-cycle methods + Initialize(mempool *Mempool) Shutdown() error // chain info IsTestnet() bool