From 93ea126123541ceb272a12b2f5dc793792b986a9 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Fri, 4 Mar 2022 17:26:13 +0100 Subject: [PATCH] Add get raw block API request #736 --- api/types.go | 5 +++++ api/worker.go | 20 ++++++++++++++++++-- bchain/basechain.go | 5 +++++ bchain/coins/bch/bcashrpc.go | 21 +++++++++++++++------ bchain/coins/bitcore/bitcorerpc.go | 2 +- bchain/coins/blockchain.go | 5 +++++ bchain/coins/btc/bitcoinrpc.go | 25 +++++++++++++++++-------- bchain/coins/firo/firorpc.go | 24 +++++++++++++++++------- bchain/coins/flo/florpc.go | 2 +- bchain/coins/zec/zcashrpc.go | 7 ++++--- bchain/types.go | 1 + server/public.go | 12 ++++++++++++ server/public_test.go | 9 +++++++++ tests/dbtestdata/fakechain.go | 5 +++++ 14 files changed, 115 insertions(+), 28 deletions(-) diff --git a/api/types.go b/api/types.go index 9295558a..400b1585 100644 --- a/api/types.go +++ b/api/types.go @@ -399,6 +399,11 @@ type Block struct { Transactions []*Tx `json:"txs,omitempty"` } +// BlockRaw contains raw block in hex +type BlockRaw struct { + Hex string `json:"hex"` +} + // BlockbookInfo contains information about the running blockbook instance type BlockbookInfo struct { Coin string `json:"coin"` diff --git a/api/worker.go b/api/worker.go index 31328a79..81771980 100644 --- a/api/worker.go +++ b/api/worker.go @@ -1486,8 +1486,8 @@ func (w *Worker) GetFiatRatesTickersList(timestamp int64) (*db.ResultTickerListA }, nil } -// getBlockInfoFromBlockID returns block info from block height or block hash -func (w *Worker) getBlockInfoFromBlockID(bid string) (*bchain.BlockInfo, error) { +// getBlockHashBlockID returns block hash from block height or block hash +func (w *Worker) getBlockHashBlockID(bid string) string { // try to decide if passed string (bid) is block height or block hash // if it's a number, must be less than int32 var hash string @@ -1500,6 +1500,12 @@ func (w *Worker) getBlockInfoFromBlockID(bid string) (*bchain.BlockInfo, error) } else { hash = bid } + return hash +} + +// getBlockInfoFromBlockID returns block info from block height or block hash +func (w *Worker) getBlockInfoFromBlockID(bid string) (*bchain.BlockInfo, error) { + hash:=w.getBlockHashBlockID(bid) if hash == "" { return nil, NewAPIError("Block not found", true) } @@ -1678,6 +1684,16 @@ func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) { }, nil } +// GetBlock returns paged data about block +func (w *Worker) GetBlockRaw(bid string) (*BlockRaw, error) { + hash:=w.getBlockHashBlockID(bid) + if hash == "" { + return nil, NewAPIError("Block not found", true) + } + hex, err := w.chain.GetBlockRaw(hash) + return &BlockRaw{Hex: hex}, err +} + // ComputeFeeStats computes fee distribution in defined blocks and logs them to log func (w *Worker) ComputeFeeStats(blockFrom, blockTo int, stopCompute chan os.Signal) error { bestheight, _, err := w.db.GetBestBlock() diff --git a/bchain/basechain.go b/bchain/basechain.go index f1a58e35..26ea6a5e 100644 --- a/bchain/basechain.go +++ b/bchain/basechain.go @@ -29,6 +29,11 @@ func (b *BaseChain) GetNetworkName() string { return b.Network } +// GetBlockRaw is not supported by default +func (b *BaseChain) GetBlockRaw(hash string) (string, error) { + return "", errors.New("GetBlockRaw: not supported") +} + // GetMempoolEntry is not supported by default func (b *BaseChain) GetMempoolEntry(txid string) (*MempoolEntry, error) { return nil, errors.New("GetMempoolEntry: not supported") diff --git a/bchain/coins/bch/bcashrpc.go b/bchain/coins/bch/bcashrpc.go index 9203479b..b06fd6bb 100644 --- a/bchain/coins/bch/bcashrpc.go +++ b/bchain/coins/bch/bcashrpc.go @@ -95,7 +95,7 @@ func (b *BCashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { if err != nil { return nil, err } - data, err := b.GetBlockRaw(hash) + data, err := b.GetBlockBytes(hash) if err != nil { return nil, err } @@ -111,7 +111,7 @@ func (b *BCashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { } // GetBlockRaw returns block with given hash as bytes. -func (b *BCashRPC) GetBlockRaw(hash string) ([]byte, error) { +func (b *BCashRPC) GetBlockRaw(hash string) (string, error) { glog.V(1).Info("rpc: getblock (verbose=0) ", hash) res := btc.ResGetBlockRaw{} @@ -121,15 +121,24 @@ func (b *BCashRPC) GetBlockRaw(hash string) ([]byte, error) { err := b.Call(&req, &res) if err != nil { - return nil, errors.Annotatef(err, "hash %v", hash) + return "", errors.Annotatef(err, "hash %v", hash) } if res.Error != nil { if isErrBlockNotFound(res.Error) { - return nil, bchain.ErrBlockNotFound + return "", bchain.ErrBlockNotFound } - return nil, errors.Annotatef(res.Error, "hash %v", hash) + return "", errors.Annotatef(res.Error, "hash %v", hash) } - return hex.DecodeString(res.Result) + return res.Result,nil +} + +// GetBlockBytes returns block with given hash as bytes +func (b *BCashRPC) GetBlockBytes(hash string) ([]byte, error) { + block,err:=b.GetBlockRaw(hash) + if err != nil { + return nil,err + } + return hex.DecodeString(block) } // GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids diff --git a/bchain/coins/bitcore/bitcorerpc.go b/bchain/coins/bitcore/bitcorerpc.go index 60cc3109..97da4044 100644 --- a/bchain/coins/bitcore/bitcorerpc.go +++ b/bchain/coins/bitcore/bitcorerpc.go @@ -78,7 +78,7 @@ func (f *BitcoreRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) if err != nil { return nil, err } - data, err := f.GetBlockRaw(hash) + data, err := f.GetBlockBytes(hash) if err != nil { return nil, err } diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index c3582b66..7c864b49 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -254,6 +254,11 @@ func (c *blockChainWithMetrics) GetBlockInfo(hash string) (v *bchain.BlockInfo, return c.b.GetBlockInfo(hash) } +func (c *blockChainWithMetrics) GetBlockRaw(hash string) (v string, err error) { + defer func(s time.Time) { c.observeRPCLatency("GetBlockRaw", s, err) }(time.Now()) + return c.b.GetBlockRaw(hash) +} + func (c *blockChainWithMetrics) GetMempoolTransactions() (v []string, err error) { defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactions", s, err) }(time.Now()) return c.b.GetMempoolTransactions() diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index 50077f45..0a0cca97 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -551,7 +551,7 @@ func (b *BitcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) if err != nil { return nil, err } - data, err := b.GetBlockRaw(hash) + data, err := b.GetBlockBytes(hash) if err != nil { return nil, err } @@ -588,7 +588,7 @@ func (b *BitcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, 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) { - data, err := b.GetBlockRaw(hash) + data, err := b.GetBlockBytes(hash) if err != nil { return nil, err } @@ -601,8 +601,8 @@ func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain. return block, nil } -// GetBlockRaw returns block with given hash as bytes -func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) { +// GetBlockRaw returns block with given hash as hex string +func (b *BitcoinRPC) GetBlockRaw(hash string) (string, error) { glog.V(1).Info("rpc: getblock (verbosity=0) ", hash) res := ResGetBlockRaw{} @@ -612,15 +612,24 @@ func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) { err := b.Call(&req, &res) if err != nil { - return nil, errors.Annotatef(err, "hash %v", hash) + return "", errors.Annotatef(err, "hash %v", hash) } if res.Error != nil { if IsErrBlockNotFound(res.Error) { - return nil, bchain.ErrBlockNotFound + return "", bchain.ErrBlockNotFound } - return nil, errors.Annotatef(res.Error, "hash %v", hash) + return "", errors.Annotatef(res.Error, "hash %v", hash) } - return hex.DecodeString(res.Result) + return res.Result,nil +} + +// GetBlockBytes returns block with given hash as bytes +func (b *BitcoinRPC) GetBlockBytes(hash string) ([]byte, error) { + block,err:=b.GetBlockRaw(hash) + if err != nil { + return nil,err + } + return hex.DecodeString(block) } // GetBlockFull returns block with given hash diff --git a/bchain/coins/firo/firorpc.go b/bchain/coins/firo/firorpc.go index 1ff861bf..67a19287 100644 --- a/bchain/coins/firo/firorpc.go +++ b/bchain/coins/firo/firorpc.go @@ -81,7 +81,7 @@ func (zc *FiroRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { return nil, err } - data, err := zc.GetBlockRaw(hash) + data, err := zc.GetBlockBytes(hash) if err != nil { return nil, err } @@ -118,7 +118,7 @@ func (zc *FiroRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { } func (zc *FiroRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) { - data, err := zc.GetBlockRaw(hash) + data, err := zc.GetBlockBytes(hash) if err != nil { return nil, err } @@ -134,7 +134,8 @@ func (zc *FiroRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Bl return block, nil } -func (zc *FiroRPC) GetBlockRaw(hash string) ([]byte, error) { +// GetBlockRaw returns block with given hash as hex string +func (zc *FiroRPC) GetBlockRaw(hash string) (string, error) { glog.V(1).Info("rpc: getblock (verbosity=false) ", hash) res := btc.ResGetBlockRaw{} @@ -144,15 +145,24 @@ func (zc *FiroRPC) GetBlockRaw(hash string) ([]byte, error) { err := zc.Call(&req, &res) if err != nil { - return nil, errors.Annotatef(err, "hash %v", hash) + return "", errors.Annotatef(err, "hash %v", hash) } if res.Error != nil { if btc.IsErrBlockNotFound(res.Error) { - return nil, bchain.ErrBlockNotFound + return "", bchain.ErrBlockNotFound } - return nil, errors.Annotatef(res.Error, "hash %v", hash) + return "", errors.Annotatef(res.Error, "hash %v", hash) } - return hex.DecodeString(res.Result) + return res.Result,nil +} + +// GetBlockBytes returns block with given hash as bytes +func (zc *FiroRPC) GetBlockBytes(hash string) ([]byte, error) { + block,err:=zc.GetBlockRaw(hash) + if err != nil { + return nil,err + } + return hex.DecodeString(block) } func (zc *FiroRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { diff --git a/bchain/coins/flo/florpc.go b/bchain/coins/flo/florpc.go index a77ea3b2..a875af9a 100644 --- a/bchain/coins/flo/florpc.go +++ b/bchain/coins/flo/florpc.go @@ -78,7 +78,7 @@ func (f *FloRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { if err != nil { return nil, err } - data, err := f.GetBlockRaw(hash) + data, err := f.GetBlockBytes(hash) if err != nil { return nil, err } diff --git a/bchain/coins/zec/zcashrpc.go b/bchain/coins/zec/zcashrpc.go index 3abeaff2..3c740d16 100644 --- a/bchain/coins/zec/zcashrpc.go +++ b/bchain/coins/zec/zcashrpc.go @@ -164,7 +164,8 @@ 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" +// GetBlockRaw is not supported +func (z *ZCashRPC) GetBlockRaw(hash string) (string, error) { + return "", errors.New("GetBlockRaw: not supported") } + diff --git a/bchain/types.go b/bchain/types.go index dd7e7cd6..29d8f2b2 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -285,6 +285,7 @@ type BlockChain interface { GetBlockHeader(hash string) (*BlockHeader, error) GetBlock(hash string, height uint32) (*Block, error) GetBlockInfo(hash string) (*BlockInfo, error) + GetBlockRaw(hash string) (string, error) GetMempoolTransactions() ([]string, error) GetTransaction(txid string) (*Tx, error) GetTransactionForMempool(txid string) (*Tx, error) diff --git a/server/public.go b/server/public.go index 6e9a2117..6bcf927f 100644 --- a/server/public.go +++ b/server/public.go @@ -174,6 +174,7 @@ func (s *PublicServer) ConnectFullPublicInterface() { serveMux.HandleFunc(path+"api/xpub/", s.jsonHandler(s.apiXpub, apiDefault)) serveMux.HandleFunc(path+"api/utxo/", s.jsonHandler(s.apiUtxo, apiDefault)) serveMux.HandleFunc(path+"api/block/", s.jsonHandler(s.apiBlock, apiDefault)) + serveMux.HandleFunc(path+"api/rawblock/", s.jsonHandler(s.apiBlockRaw, apiDefault)) serveMux.HandleFunc(path+"api/sendtx/", s.jsonHandler(s.apiSendTx, apiDefault)) serveMux.HandleFunc(path+"api/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiDefault)) serveMux.HandleFunc(path+"api/balancehistory/", s.jsonHandler(s.apiBalanceHistory, apiDefault)) @@ -185,6 +186,7 @@ func (s *PublicServer) ConnectFullPublicInterface() { serveMux.HandleFunc(path+"api/v2/xpub/", s.jsonHandler(s.apiXpub, apiV2)) serveMux.HandleFunc(path+"api/v2/utxo/", s.jsonHandler(s.apiUtxo, apiV2)) serveMux.HandleFunc(path+"api/v2/block/", s.jsonHandler(s.apiBlock, apiV2)) + serveMux.HandleFunc(path+"api/v2/rawblock/", s.jsonHandler(s.apiBlockRaw, apiDefault)) serveMux.HandleFunc(path+"api/v2/sendtx/", s.jsonHandler(s.apiSendTx, apiV2)) serveMux.HandleFunc(path+"api/v2/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV2)) serveMux.HandleFunc(path+"api/v2/feestats/", s.jsonHandler(s.apiFeeStats, apiV2)) @@ -1139,6 +1141,16 @@ func (s *PublicServer) apiBlock(r *http.Request, apiVersion int) (interface{}, e return block, err } +func (s *PublicServer) apiBlockRaw(r *http.Request, apiVersion int) (interface{}, error) { + var block *api.BlockRaw + var err error + s.metrics.ExplorerViews.With(common.Labels{"action": "api-block-raw"}).Inc() + if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { + block, err = s.api.GetBlockRaw(r.URL.Path[i+1:]) + } + return block, err +} + func (s *PublicServer) apiFeeStats(r *http.Request, apiVersion int) (interface{}, error) { var feeStats *api.FeeStats var err error diff --git a/server/public_test.go b/server/public_test.go index 849a2687..bd842373 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -937,6 +937,15 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { `{"page":1,"totalPages":1,"itemsOnPage":1000,"hash":"0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997","nextBlockHash":"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6","height":225493,"confirmations":2,"size":1234567,"time":1521515026,"version":0,"merkleRoot":"","nonce":"","bits":"","difficulty":"","txCount":2,"txs":[{"txid":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","vin":[],"vout":[{"value":"100000000","n":0,"addresses":["mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti"],"isAddress":true},{"value":"12345","n":1,"spent":true,"addresses":["mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"],"isAddress":true},{"value":"12345","n":2,"addresses":["mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"],"isAddress":true}],"blockHash":"0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997","blockHeight":225493,"confirmations":2,"blockTime":1521515026,"value":"100024690","valueIn":"0","fees":"0"},{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","vin":[],"vout":[{"value":"1234567890123","n":0,"spent":true,"addresses":["mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw"],"isAddress":true},{"value":"1","n":1,"spent":true,"addresses":["2MzmAKayJmja784jyHvRUW1bXPget1csRRG"],"isAddress":true},{"value":"9876","n":2,"spent":true,"addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"],"isAddress":true}],"blockHash":"0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997","blockHeight":225493,"confirmations":2,"blockTime":1521515026,"value":"1234567900000","valueIn":"0","fees":"0"}]}`, }, }, + { + name: "apiGetRawBlock", + r: newGetRequest(ts.URL + "/api/v2/rawblock/225493"), + status: http.StatusOK, + contentType: "application/json; charset=utf-8", + body: []string{ + `{"hex":"00e0ff3fd42677a86f1515bafcf9802c1765e02226655a9b97fd44132602000000000000"}`, + }, + }, } for _, tt := range tests { diff --git a/tests/dbtestdata/fakechain.go b/tests/dbtestdata/fakechain.go index bf19dfb9..a20fdd40 100644 --- a/tests/dbtestdata/fakechain.go +++ b/tests/dbtestdata/fakechain.go @@ -136,6 +136,11 @@ func getTxInBlock(b *bchain.Block, txid string) *bchain.Tx { return nil } +func (c *fakeBlockChain) GetBlockRaw(hash string) (string, error) { + return "00e0ff3fd42677a86f1515bafcf9802c1765e02226655a9b97fd44132602000000000000", nil +} + + func (c *fakeBlockChain) GetTransaction(txid string) (v *bchain.Tx, err error) { v = getTxInBlock(GetTestBitcoinTypeBlock1(c.Parser), txid) if v == nil {