2018-03-08 10:36:01 -07:00
|
|
|
package btc
|
2017-08-28 09:50:57 -06:00
|
|
|
|
|
|
|
import (
|
2017-09-12 10:08:01 -06:00
|
|
|
"bytes"
|
2018-05-30 06:37:30 -06:00
|
|
|
"context"
|
2017-08-28 09:50:57 -06:00
|
|
|
"encoding/hex"
|
2017-09-12 18:29:44 -06:00
|
|
|
"encoding/json"
|
2018-03-02 05:49:32 -07:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2018-07-24 07:58:37 -06:00
|
|
|
"math/big"
|
2018-03-02 05:54:21 -07:00
|
|
|
"net"
|
2017-08-28 09:50:57 -06:00
|
|
|
"net/http"
|
2018-11-28 06:27:02 -07:00
|
|
|
"runtime/debug"
|
2017-08-28 09:50:57 -06:00
|
|
|
"time"
|
2018-01-30 10:22:25 -07:00
|
|
|
|
|
|
|
"github.com/golang/glog"
|
2018-03-01 03:06:10 -07:00
|
|
|
"github.com/juju/errors"
|
2019-01-04 05:36:16 -07:00
|
|
|
"github.com/martinboehm/btcd/wire"
|
2020-02-26 09:17:43 -07:00
|
|
|
"github.com/trezor/blockbook/bchain"
|
2020-05-09 16:03:22 -06:00
|
|
|
"github.com/trezor/blockbook/common"
|
2017-08-28 09:50:57 -06:00
|
|
|
)
|
|
|
|
|
2018-03-08 04:59:37 -07:00
|
|
|
// BitcoinRPC is an interface to JSON-RPC bitcoind service.
|
|
|
|
type BitcoinRPC struct {
|
2018-11-28 06:27:02 -07:00
|
|
|
*bchain.BaseChain
|
2018-06-09 06:37:05 -06:00
|
|
|
client http.Client
|
|
|
|
rpcURL string
|
|
|
|
user string
|
|
|
|
password string
|
2018-11-06 10:41:13 -07:00
|
|
|
Mempool *bchain.MempoolBitcoinType
|
2018-06-09 06:37:05 -06:00
|
|
|
ParseBlocks bool
|
|
|
|
pushHandler func(bchain.NotificationType)
|
|
|
|
mq *bchain.MQ
|
|
|
|
ChainConfig *Configuration
|
|
|
|
RPCMarshaler RPCMarshaler
|
2018-03-15 05:34:29 -06:00
|
|
|
}
|
|
|
|
|
2018-10-31 10:17:22 -06:00
|
|
|
// Configuration represents json config file
|
2018-05-28 06:57:44 -06:00
|
|
|
type Configuration struct {
|
2019-04-17 07:47:08 -06:00
|
|
|
CoinName string `json:"coin_name"`
|
|
|
|
CoinShortcut string `json:"coin_shortcut"`
|
|
|
|
RPCURL string `json:"rpc_url"`
|
|
|
|
RPCUser string `json:"rpc_user"`
|
|
|
|
RPCPass string `json:"rpc_pass"`
|
|
|
|
RPCTimeout int `json:"rpc_timeout"`
|
|
|
|
Parse bool `json:"parse"`
|
|
|
|
MessageQueueBinding string `json:"message_queue_binding"`
|
|
|
|
Subversion string `json:"subversion"`
|
|
|
|
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
|
|
|
MempoolWorkers int `json:"mempool_workers"`
|
|
|
|
MempoolSubWorkers int `json:"mempool_sub_workers"`
|
|
|
|
AddressFormat string `json:"address_format"`
|
|
|
|
SupportsEstimateFee bool `json:"supports_estimate_fee"`
|
|
|
|
SupportsEstimateSmartFee bool `json:"supports_estimate_smart_fee"`
|
|
|
|
XPubMagic uint32 `json:"xpub_magic,omitempty"`
|
|
|
|
XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"`
|
|
|
|
XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"`
|
|
|
|
Slip44 uint32 `json:"slip44,omitempty"`
|
Add fiat rates functionality (#316)
* Add initial commit for fiat rates functionality
* templates.go: use bash from current user's environment
* bitcoinrpc.go: add FiatRates and FiatRatesParams to config
* blockbook.go: add initFiatRatesDownloader kickoff
* bitcoin.json: add coingecko API URL
* rockdb.go: add FindTicker and StoreTicker functions
* rocksdb_test.go: add a simple test for storing and getting FiatRate tickers
* rocksdb: add FindLastTicker and convertDate, make FindTicker return strings
* rocksdb: add ConvertDate function and CoinGeckoTicker struct, update tests
* blockbook.go, fiat: finalize the CoinGecko downloader
* coingecko.go: do not stop syncing when encountered an error
* rocksdb_test: fix the exported function name
* worker.go: make getBlockInfoFromBlockID a public function
* public.go: apiTickers kickoff
* rocksdb_test: fix the unittest comment
* coingecko.go: update comments
* blockbook.go, fiat: reword CoinGecko -> FiatRates, fix binary search upper bound, remove assignment of goroutine call result
* rename coingecko -> fiat_rates
* fiat_rates: export only the necessary methods
* blockbook.go: update log message
* bitcoinrpc.go: remove fiatRates settings
* use CurrencyRatesTicker structure everywhere, fix time format string, update tests, use UTC time
* add /api/v2/tickers tests, store rates as strings (json.Number)
* fiat_rates: add more tests, metrics and tickers-list endpoint, make the "currency" parameter mandatory
* public, worker: move FiatRates API logic to worker.go
* fiat_rates: add a future date test, fix comments, add more checks, store time as a pointer
* rocksdb_test: remove unneeded code
* fiat_rates: add a "ping" call to check server availability
* fiat_rates: do not return empty ticker, return nil instead if not found
add a test for non-existent ticker
* rocksdb_test: remove Sleep from tests
* worker.go: do not propagate all API errors to the client
* move InitTestFiatRates from rocksdb.go to public_test.go
* public.go: fix FiatRatesFindLastTicker result check
* fiat_rates: mock API server responses
* remove commented-out code
* fiat_rates: add comment explaining what periodSeconds attribute is used for
* websocket.go: implement fiatRates websocket endpoints & add tests
* fiatRates: add getFiatRatesTickersList websocket endpoint & test
* fiatRates: make websocket getFiatRatesByDate accept an array of dates, add more tests
* fiatRates: remove getFiatRatesForBlockID from websocket endpoints
* fiatRates: remove "if test", use custom startTime instead
Update tests and mock data
* fiatRates: finalize websocket functionality
add "date" parameter to TickerList
return data timestamps where needed
fix sync bugs (nil timestamp, duplicate save)
* fiatRates: add FiatRates configs for different coins
* worker.go: make GetBlockInfoFromBlockID private again
* fiatRates: wait & retry on errors, remove Ping function
* websocket.go: remove incorrect comment
* fiatRates: move coingecko-related code to a separate file, use interface
* fiatRates: if the new rates are the same as previous, try five more times, and only then store them
* coingecko: fix getting actual rates, add a timestamp parameter to get uncached responses
* vertcoin_testnet.json: remove fiat rates parameters
* fiat_rates: add timestamp to log message about skipping the repeating rates
2019-12-17 02:40:02 -07:00
|
|
|
AlternativeEstimateFee string `json:"alternative_estimate_fee,omitempty"`
|
|
|
|
AlternativeEstimateFeeParams string `json:"alternative_estimate_fee_params,omitempty"`
|
2019-07-23 04:52:18 -06:00
|
|
|
MinimumCoinbaseConfirmations int `json:"minimumCoinbaseConfirmations,omitempty"`
|
2018-03-08 04:59:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewBitcoinRPC returns new BitcoinRPC instance.
|
2018-03-27 15:39:06 -06:00
|
|
|
func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
2018-03-15 05:34:29 -06:00
|
|
|
var err error
|
2018-05-28 06:57:44 -06:00
|
|
|
var c Configuration
|
2018-03-15 05:34:29 -06:00
|
|
|
err = json.Unmarshal(config, &c)
|
|
|
|
if err != nil {
|
2018-03-19 05:05:58 -06:00
|
|
|
return nil, errors.Annotatef(err, "Invalid configuration file")
|
2018-03-15 05:34:29 -06:00
|
|
|
}
|
2018-05-28 06:57:44 -06:00
|
|
|
// keep at least 100 mappings block->addresses to allow rollback
|
|
|
|
if c.BlockAddressesToKeep < 100 {
|
|
|
|
c.BlockAddressesToKeep = 100
|
|
|
|
}
|
2019-07-23 04:52:18 -06:00
|
|
|
// default MinimumCoinbaseConfirmations is 100
|
|
|
|
if c.MinimumCoinbaseConfirmations == 0 {
|
|
|
|
c.MinimumCoinbaseConfirmations = 100
|
|
|
|
}
|
2018-05-28 08:04:05 -06:00
|
|
|
// at least 1 mempool worker/subworker for synchronous mempool synchronization
|
|
|
|
if c.MempoolWorkers < 1 {
|
|
|
|
c.MempoolWorkers = 1
|
|
|
|
}
|
|
|
|
if c.MempoolSubWorkers < 1 {
|
|
|
|
c.MempoolSubWorkers = 1
|
|
|
|
}
|
2018-07-24 07:58:37 -06:00
|
|
|
// btc supports both calls, other coins overriding BitcoinRPC can change this
|
|
|
|
c.SupportsEstimateFee = true
|
|
|
|
c.SupportsEstimateSmartFee = true
|
2018-05-28 08:04:05 -06:00
|
|
|
|
2018-03-08 04:59:37 -07:00
|
|
|
transport := &http.Transport{
|
|
|
|
Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial,
|
|
|
|
MaxIdleConns: 100,
|
|
|
|
MaxIdleConnsPerHost: 100, // necessary to not to deplete ports
|
|
|
|
}
|
2018-03-15 05:34:29 -06:00
|
|
|
|
2018-03-08 04:59:37 -07:00
|
|
|
s := &BitcoinRPC{
|
2018-11-28 06:27:02 -07:00
|
|
|
BaseChain: &bchain.BaseChain{},
|
2018-06-09 06:37:05 -06:00
|
|
|
client: http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport},
|
|
|
|
rpcURL: c.RPCURL,
|
|
|
|
user: c.RPCUser,
|
|
|
|
password: c.RPCPass,
|
|
|
|
ParseBlocks: c.Parse,
|
|
|
|
ChainConfig: &c,
|
|
|
|
pushHandler: pushHandler,
|
|
|
|
RPCMarshaler: JSONMarshalerV2{},
|
2018-03-08 04:59:37 -07:00
|
|
|
}
|
2018-03-20 10:28:03 -06:00
|
|
|
|
2018-05-17 03:52:16 -06:00
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize initializes BitcoinRPC instance.
|
2018-03-21 08:33:48 -06:00
|
|
|
func (b *BitcoinRPC) Initialize() error {
|
2018-10-03 04:29:44 -06:00
|
|
|
b.ChainConfig.SupportsEstimateFee = false
|
2018-03-20 10:28:03 -06:00
|
|
|
|
2019-03-25 09:43:57 -06:00
|
|
|
ci, err := b.GetChainInfo()
|
2018-03-20 10:28:03 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-03-25 09:43:57 -06:00
|
|
|
chainName := ci.Chain
|
2018-03-20 10:28:03 -06:00
|
|
|
|
|
|
|
params := GetChainParams(chainName)
|
2018-03-08 04:59:37 -07:00
|
|
|
|
|
|
|
// always create parser
|
2018-05-28 06:57:44 -06:00
|
|
|
b.Parser = NewBitcoinParser(params, b.ChainConfig)
|
2018-03-08 04:59:37 -07:00
|
|
|
|
|
|
|
// parameters for getInfo request
|
2018-03-20 10:28:03 -06:00
|
|
|
if params.Net == wire.MainNet {
|
|
|
|
b.Testnet = false
|
|
|
|
b.Network = "livenet"
|
2018-03-08 04:59:37 -07:00
|
|
|
} else {
|
2018-03-20 10:28:03 -06:00
|
|
|
b.Testnet = true
|
|
|
|
b.Network = "testnet"
|
2018-03-08 04:59:37 -07:00
|
|
|
}
|
|
|
|
|
2018-03-20 10:28:03 -06:00
|
|
|
glog.Info("rpc: block chain ", params.Name)
|
2018-03-19 04:02:19 -06:00
|
|
|
|
2019-04-17 07:47:08 -06:00
|
|
|
if b.ChainConfig.AlternativeEstimateFee == "whatthefee" {
|
|
|
|
if err = InitWhatTheFee(b, b.ChainConfig.AlternativeEstimateFeeParams); err != nil {
|
|
|
|
glog.Error("InitWhatTheFee error ", err, " Reverting to default estimateFee functionality")
|
|
|
|
// disable AlternativeEstimateFee logic
|
|
|
|
b.ChainConfig.AlternativeEstimateFee = ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-20 10:28:03 -06:00
|
|
|
return nil
|
2018-03-08 04:59:37 -07:00
|
|
|
}
|
|
|
|
|
2019-03-25 09:43:57 -06:00
|
|
|
// CreateMempool creates mempool if not already created, however does not initialize it
|
2019-04-03 14:08:32 -06:00
|
|
|
func (b *BitcoinRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) {
|
2019-03-25 09:43:57 -06:00
|
|
|
if b.Mempool == nil {
|
2019-04-03 14:08:32 -06:00
|
|
|
b.Mempool = bchain.NewMempoolBitcoinType(chain, b.ChainConfig.MempoolWorkers, b.ChainConfig.MempoolSubWorkers)
|
2019-03-25 09:43:57 -06:00
|
|
|
}
|
|
|
|
return b.Mempool, nil
|
|
|
|
}
|
|
|
|
|
2019-03-29 10:01:20 -06:00
|
|
|
// InitializeMempool creates ZeroMQ subscription and sets AddrDescForOutpointFunc to the Mempool
|
2019-04-01 09:00:34 -06:00
|
|
|
func (b *BitcoinRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc) error {
|
2019-03-29 10:01:20 -06:00
|
|
|
if b.Mempool == nil {
|
|
|
|
return errors.New("Mempool not created")
|
|
|
|
}
|
|
|
|
b.Mempool.AddrDescForOutpoint = addrDescForOutpoint
|
2019-04-01 09:00:34 -06:00
|
|
|
b.Mempool.OnNewTxAddr = onNewTxAddr
|
2019-03-25 09:43:57 -06:00
|
|
|
if b.mq == nil {
|
|
|
|
mq, err := bchain.NewMQ(b.ChainConfig.MessageQueueBinding, b.pushHandler)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error("mq: ", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
b.mq = mq
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shutdown ZeroMQ and other resources
|
2018-05-30 06:37:30 -06:00
|
|
|
func (b *BitcoinRPC) Shutdown(ctx context.Context) error {
|
2018-03-15 05:34:29 -06:00
|
|
|
if b.mq != nil {
|
2018-05-30 06:37:30 -06:00
|
|
|
if err := b.mq.Shutdown(ctx); err != nil {
|
2018-03-15 05:34:29 -06:00
|
|
|
glog.Error("MQ.Shutdown error: ", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-25 09:43:57 -06:00
|
|
|
// GetCoinName returns the coin name
|
2018-06-05 08:14:46 -06:00
|
|
|
func (b *BitcoinRPC) GetCoinName() string {
|
|
|
|
return b.ChainConfig.CoinName
|
|
|
|
}
|
|
|
|
|
2019-03-25 09:43:57 -06:00
|
|
|
// GetSubversion returns the backend subversion
|
2018-04-27 02:53:33 -06:00
|
|
|
func (b *BitcoinRPC) GetSubversion() string {
|
2018-05-28 06:57:44 -06:00
|
|
|
return b.ChainConfig.Subversion
|
2018-04-27 02:53:33 -06:00
|
|
|
}
|
|
|
|
|
2017-09-12 10:08:01 -06:00
|
|
|
// getblockhash
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdGetBlockHash struct {
|
2017-09-12 10:08:01 -06:00
|
|
|
Method string `json:"method"`
|
|
|
|
Params struct {
|
|
|
|
Height uint32 `json:"height"`
|
|
|
|
} `json:"params"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetBlockHash struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result string `json:"result"`
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// getbestblockhash
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdGetBestBlockHash struct {
|
2017-09-12 10:08:01 -06:00
|
|
|
Method string `json:"method"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetBestBlockHash struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result string `json:"result"`
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|
|
|
|
|
2018-01-30 01:45:47 -07:00
|
|
|
// getblockcount
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdGetBlockCount struct {
|
2018-01-30 01:45:47 -07:00
|
|
|
Method string `json:"method"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetBlockCount struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result uint32 `json:"result"`
|
2018-01-30 01:45:47 -07:00
|
|
|
}
|
|
|
|
|
2018-03-07 01:38:19 -07:00
|
|
|
// getblockchaininfo
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdGetBlockChainInfo struct {
|
2018-03-07 01:38:19 -07:00
|
|
|
Method string `json:"method"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetBlockChainInfo struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
2018-03-07 01:38:19 -07:00
|
|
|
Result struct {
|
2020-05-09 16:03:22 -06:00
|
|
|
Chain string `json:"chain"`
|
|
|
|
Blocks int `json:"blocks"`
|
|
|
|
Headers int `json:"headers"`
|
|
|
|
Bestblockhash string `json:"bestblockhash"`
|
|
|
|
Difficulty common.JSONNumber `json:"difficulty"`
|
|
|
|
SizeOnDisk int64 `json:"size_on_disk"`
|
|
|
|
Warnings string `json:"warnings"`
|
2018-09-14 06:48:43 -06:00
|
|
|
} `json:"result"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// getnetworkinfo
|
|
|
|
|
|
|
|
type CmdGetNetworkInfo struct {
|
|
|
|
Method string `json:"method"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ResGetNetworkInfo struct {
|
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result struct {
|
2020-05-09 16:03:22 -06:00
|
|
|
Version common.JSONNumber `json:"version"`
|
|
|
|
Subversion common.JSONNumber `json:"subversion"`
|
|
|
|
ProtocolVersion common.JSONNumber `json:"protocolversion"`
|
|
|
|
Timeoffset float64 `json:"timeoffset"`
|
|
|
|
Warnings string `json:"warnings"`
|
2018-03-07 01:38:19 -07:00
|
|
|
} `json:"result"`
|
|
|
|
}
|
|
|
|
|
2018-01-31 07:04:54 -07:00
|
|
|
// getrawmempool
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdGetMempool struct {
|
2018-01-31 07:04:54 -07:00
|
|
|
Method string `json:"method"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetMempool struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result []string `json:"result"`
|
2018-01-31 07:04:54 -07:00
|
|
|
}
|
|
|
|
|
2017-09-12 10:08:01 -06:00
|
|
|
// getblockheader
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdGetBlockHeader struct {
|
2017-09-12 10:08:01 -06:00
|
|
|
Method string `json:"method"`
|
|
|
|
Params struct {
|
|
|
|
BlockHash string `json:"blockhash"`
|
|
|
|
Verbose bool `json:"verbose"`
|
|
|
|
} `json:"params"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetBlockHeader struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result bchain.BlockHeader `json:"result"`
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// getblock
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdGetBlock struct {
|
2017-09-12 10:08:01 -06:00
|
|
|
Method string `json:"method"`
|
|
|
|
Params struct {
|
|
|
|
BlockHash string `json:"blockhash"`
|
2017-09-12 16:36:08 -06:00
|
|
|
Verbosity int `json:"verbosity"`
|
2017-09-12 10:08:01 -06:00
|
|
|
} `json:"params"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetBlockRaw struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result string `json:"result"`
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|
|
|
|
|
2018-09-17 10:28:08 -06:00
|
|
|
type BlockThin struct {
|
|
|
|
bchain.BlockHeader
|
|
|
|
Txids []string `json:"tx"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetBlockThin struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
2018-09-17 10:28:08 -06:00
|
|
|
Result BlockThin `json:"result"`
|
2017-09-12 16:36:08 -06:00
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetBlockFull struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result bchain.Block `json:"result"`
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|
|
|
|
|
2018-09-17 10:28:08 -06:00
|
|
|
type ResGetBlockInfo struct {
|
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result bchain.BlockInfo `json:"result"`
|
|
|
|
}
|
|
|
|
|
2017-09-12 10:08:01 -06:00
|
|
|
// getrawtransaction
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdGetRawTransaction struct {
|
2017-09-12 10:08:01 -06:00
|
|
|
Method string `json:"method"`
|
|
|
|
Params struct {
|
|
|
|
Txid string `json:"txid"`
|
|
|
|
Verbose bool `json:"verbose"`
|
|
|
|
} `json:"params"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetRawTransaction struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
2018-05-18 07:04:40 -06:00
|
|
|
Result json.RawMessage `json:"result"`
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetRawTransactionNonverbose struct {
|
2018-05-14 10:12:01 -06:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result string `json:"result"`
|
|
|
|
}
|
|
|
|
|
2018-02-07 11:59:09 -07:00
|
|
|
// estimatesmartfee
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdEstimateSmartFee struct {
|
2018-02-07 11:59:09 -07:00
|
|
|
Method string `json:"method"`
|
|
|
|
Params struct {
|
|
|
|
ConfTarget int `json:"conf_target"`
|
|
|
|
EstimateMode string `json:"estimate_mode"`
|
|
|
|
} `json:"params"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResEstimateSmartFee struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
2018-02-07 11:59:09 -07:00
|
|
|
Result struct {
|
2020-05-09 16:03:22 -06:00
|
|
|
Feerate common.JSONNumber `json:"feerate"`
|
|
|
|
Blocks int `json:"blocks"`
|
2018-02-07 11:59:09 -07:00
|
|
|
} `json:"result"`
|
|
|
|
}
|
|
|
|
|
2018-03-27 02:30:30 -06:00
|
|
|
// estimatefee
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdEstimateFee struct {
|
2018-03-27 02:30:30 -06:00
|
|
|
Method string `json:"method"`
|
|
|
|
Params struct {
|
|
|
|
Blocks int `json:"nblocks"`
|
|
|
|
} `json:"params"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResEstimateFee struct {
|
2020-05-09 16:03:22 -06:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result common.JSONNumber `json:"result"`
|
2018-03-27 02:30:30 -06:00
|
|
|
}
|
|
|
|
|
2018-02-20 04:01:42 -07:00
|
|
|
// sendrawtransaction
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdSendRawTransaction struct {
|
2018-02-20 04:01:42 -07:00
|
|
|
Method string `json:"method"`
|
|
|
|
Params []string `json:"params"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResSendRawTransaction struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result string `json:"result"`
|
2018-02-20 04:01:42 -07:00
|
|
|
}
|
|
|
|
|
2018-03-07 04:08:37 -07:00
|
|
|
// getmempoolentry
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type CmdGetMempoolEntry struct {
|
2018-03-07 04:08:37 -07:00
|
|
|
Method string `json:"method"`
|
|
|
|
Params []string `json:"params"`
|
|
|
|
}
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
type ResGetMempoolEntry struct {
|
2018-03-08 10:36:01 -07:00
|
|
|
Error *bchain.RPCError `json:"error"`
|
|
|
|
Result *bchain.MempoolEntry `json:"result"`
|
2018-03-07 04:08:37 -07:00
|
|
|
}
|
|
|
|
|
2017-09-12 10:08:01 -06:00
|
|
|
// GetBestBlockHash returns hash of the tip of the best-block-chain.
|
|
|
|
func (b *BitcoinRPC) GetBestBlockHash() (string, error) {
|
2018-01-30 10:22:25 -07:00
|
|
|
|
|
|
|
glog.V(1).Info("rpc: getbestblockhash")
|
2017-09-12 10:08:01 -06:00
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResGetBestBlockHash{}
|
|
|
|
req := CmdGetBestBlockHash{Method: "getbestblockhash"}
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2017-09-12 10:08:01 -06:00
|
|
|
|
2017-08-28 09:50:57 -06:00
|
|
|
if err != nil {
|
2017-09-12 10:08:01 -06:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if res.Error != nil {
|
|
|
|
return "", res.Error
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
2017-09-12 10:08:01 -06:00
|
|
|
return res.Result, nil
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
|
|
|
|
2018-01-30 01:45:47 -07:00
|
|
|
// GetBestBlockHeight returns height of the tip of the best-block-chain.
|
|
|
|
func (b *BitcoinRPC) GetBestBlockHeight() (uint32, error) {
|
2018-01-30 10:22:25 -07:00
|
|
|
glog.V(1).Info("rpc: getblockcount")
|
2018-01-30 01:45:47 -07:00
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResGetBlockCount{}
|
|
|
|
req := CmdGetBlockCount{Method: "getblockcount"}
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2018-01-30 01:45:47 -07:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
if res.Error != nil {
|
|
|
|
return 0, res.Error
|
|
|
|
}
|
|
|
|
return res.Result, nil
|
|
|
|
}
|
|
|
|
|
2018-09-14 06:48:43 -06:00
|
|
|
// GetChainInfo returns information about the connected backend
|
|
|
|
func (b *BitcoinRPC) GetChainInfo() (*bchain.ChainInfo, error) {
|
2018-03-07 01:38:19 -07:00
|
|
|
glog.V(1).Info("rpc: getblockchaininfo")
|
|
|
|
|
2018-09-14 06:48:43 -06:00
|
|
|
resCi := ResGetBlockChainInfo{}
|
|
|
|
err := b.Call(&CmdGetBlockChainInfo{Method: "getblockchaininfo"}, &resCi)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if resCi.Error != nil {
|
|
|
|
return nil, resCi.Error
|
|
|
|
}
|
2018-03-07 01:38:19 -07:00
|
|
|
|
2018-09-14 06:48:43 -06:00
|
|
|
glog.V(1).Info("rpc: getnetworkinfo")
|
|
|
|
resNi := ResGetNetworkInfo{}
|
|
|
|
err = b.Call(&CmdGetNetworkInfo{Method: "getnetworkinfo"}, &resNi)
|
2018-03-07 01:38:19 -07:00
|
|
|
if err != nil {
|
2018-09-14 06:48:43 -06:00
|
|
|
return nil, err
|
2018-03-07 01:38:19 -07:00
|
|
|
}
|
2018-09-14 06:48:43 -06:00
|
|
|
if resNi.Error != nil {
|
|
|
|
return nil, resNi.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
rv := &bchain.ChainInfo{
|
|
|
|
Bestblockhash: resCi.Result.Bestblockhash,
|
|
|
|
Blocks: resCi.Result.Blocks,
|
|
|
|
Chain: resCi.Result.Chain,
|
2018-09-18 02:58:47 -06:00
|
|
|
Difficulty: string(resCi.Result.Difficulty),
|
2018-09-14 06:48:43 -06:00
|
|
|
Headers: resCi.Result.Headers,
|
|
|
|
SizeOnDisk: resCi.Result.SizeOnDisk,
|
2018-09-18 02:58:47 -06:00
|
|
|
Subversion: string(resNi.Result.Subversion),
|
2018-09-14 06:48:43 -06:00
|
|
|
Timeoffset: resNi.Result.Timeoffset,
|
|
|
|
}
|
2018-09-18 02:58:47 -06:00
|
|
|
rv.Version = string(resNi.Result.Version)
|
|
|
|
rv.ProtocolVersion = string(resNi.Result.ProtocolVersion)
|
2018-09-14 06:48:43 -06:00
|
|
|
if len(resCi.Result.Warnings) > 0 {
|
|
|
|
rv.Warnings = resCi.Result.Warnings + " "
|
|
|
|
}
|
|
|
|
if resCi.Result.Warnings != resNi.Result.Warnings {
|
|
|
|
rv.Warnings += resNi.Result.Warnings
|
2018-03-07 01:38:19 -07:00
|
|
|
}
|
2018-09-14 06:48:43 -06:00
|
|
|
return rv, nil
|
2018-03-07 01:38:19 -07:00
|
|
|
}
|
|
|
|
|
2019-03-25 09:43:57 -06:00
|
|
|
// IsErrBlockNotFound returns true if error means block was not found
|
2019-01-17 12:31:15 -07:00
|
|
|
func IsErrBlockNotFound(err *bchain.RPCError) bool {
|
2018-03-26 08:44:54 -06:00
|
|
|
return err.Message == "Block not found" ||
|
|
|
|
err.Message == "Block height out of range"
|
2018-03-26 07:17:44 -06:00
|
|
|
}
|
|
|
|
|
2017-09-12 10:08:01 -06:00
|
|
|
// GetBlockHash returns hash of block in best-block-chain at given height.
|
|
|
|
func (b *BitcoinRPC) GetBlockHash(height uint32) (string, error) {
|
2018-01-30 10:22:25 -07:00
|
|
|
glog.V(1).Info("rpc: getblockhash ", height)
|
2017-09-12 10:08:01 -06:00
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResGetBlockHash{}
|
|
|
|
req := CmdGetBlockHash{Method: "getblockhash"}
|
2017-09-12 10:08:01 -06:00
|
|
|
req.Params.Height = height
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2017-09-12 10:08:01 -06:00
|
|
|
|
|
|
|
if err != nil {
|
2018-03-01 03:06:10 -07:00
|
|
|
return "", errors.Annotatef(err, "height %v", height)
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
2017-09-12 10:08:01 -06:00
|
|
|
if res.Error != nil {
|
2019-01-17 12:31:15 -07:00
|
|
|
if IsErrBlockNotFound(res.Error) {
|
2018-03-26 07:17:44 -06:00
|
|
|
return "", bchain.ErrBlockNotFound
|
|
|
|
}
|
2018-03-01 03:06:10 -07:00
|
|
|
return "", errors.Annotatef(res.Error, "height %v", height)
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|
|
|
|
return res.Result, nil
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
|
|
|
|
2017-09-12 10:08:01 -06:00
|
|
|
// GetBlockHeader returns header of block with given hash.
|
2018-03-08 10:36:01 -07:00
|
|
|
func (b *BitcoinRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
|
2018-01-30 10:22:25 -07:00
|
|
|
glog.V(1).Info("rpc: getblockheader")
|
2017-09-12 10:08:01 -06:00
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResGetBlockHeader{}
|
|
|
|
req := CmdGetBlockHeader{Method: "getblockheader"}
|
2017-09-12 10:08:01 -06:00
|
|
|
req.Params.BlockHash = hash
|
|
|
|
req.Params.Verbose = true
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2017-09-12 10:08:01 -06:00
|
|
|
|
|
|
|
if err != nil {
|
2018-03-01 03:06:10 -07:00
|
|
|
return nil, errors.Annotatef(err, "hash %v", hash)
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
2017-09-12 10:08:01 -06:00
|
|
|
if res.Error != nil {
|
2019-01-17 12:31:15 -07:00
|
|
|
if IsErrBlockNotFound(res.Error) {
|
2018-03-26 07:17:44 -06:00
|
|
|
return nil, bchain.ErrBlockNotFound
|
|
|
|
}
|
2018-03-01 03:06:10 -07:00
|
|
|
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|
|
|
|
return &res.Result, nil
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
|
|
|
|
2017-09-12 10:08:01 -06:00
|
|
|
// GetBlock returns block with given hash.
|
2018-03-12 10:37:32 -06:00
|
|
|
func (b *BitcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
2018-03-26 08:44:54 -06:00
|
|
|
var err error
|
2018-09-14 06:48:43 -06:00
|
|
|
if hash == "" {
|
2018-03-26 08:44:54 -06:00
|
|
|
hash, err = b.GetBlockHash(height)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2018-03-08 11:39:21 -07:00
|
|
|
if !b.ParseBlocks {
|
2017-09-12 16:36:08 -06:00
|
|
|
return b.GetBlockFull(hash)
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|
2018-03-12 10:37:32 -06:00
|
|
|
// optimization
|
|
|
|
if height > 0 {
|
2018-06-14 15:12:19 -06:00
|
|
|
return b.GetBlockWithoutHeader(hash, height)
|
2018-03-12 10:37:32 -06:00
|
|
|
}
|
2017-09-12 10:08:01 -06:00
|
|
|
header, err := b.GetBlockHeader(hash)
|
2017-09-06 02:59:40 -06:00
|
|
|
if err != nil {
|
2017-09-12 10:08:01 -06:00
|
|
|
return nil, err
|
2017-09-06 02:59:40 -06:00
|
|
|
}
|
2017-09-12 10:08:01 -06:00
|
|
|
data, err := b.GetBlockRaw(hash)
|
2017-08-28 09:50:57 -06:00
|
|
|
if err != nil {
|
2017-09-12 10:08:01 -06:00
|
|
|
return nil, err
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
2018-03-08 11:39:21 -07:00
|
|
|
block, err := b.Parser.ParseBlock(data)
|
2017-08-28 09:50:57 -06:00
|
|
|
if err != nil {
|
2018-03-01 03:06:10 -07:00
|
|
|
return nil, errors.Annotatef(err, "hash %v", hash)
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
2017-09-12 10:08:01 -06:00
|
|
|
block.BlockHeader = *header
|
|
|
|
return block, nil
|
|
|
|
}
|
|
|
|
|
2018-09-17 10:28:08 -06:00
|
|
|
// GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids
|
|
|
|
func (b *BitcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
|
|
|
glog.V(1).Info("rpc: getblock (verbosity=1) ", hash)
|
|
|
|
|
|
|
|
res := ResGetBlockInfo{}
|
|
|
|
req := CmdGetBlock{Method: "getblock"}
|
|
|
|
req.Params.BlockHash = hash
|
|
|
|
req.Params.Verbosity = 1
|
|
|
|
err := b.Call(&req, &res)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Annotatef(err, "hash %v", hash)
|
|
|
|
}
|
|
|
|
if res.Error != nil {
|
2019-01-17 12:31:15 -07:00
|
|
|
if IsErrBlockNotFound(res.Error) {
|
2018-09-17 10:28:08 -06:00
|
|
|
return nil, bchain.ErrBlockNotFound
|
|
|
|
}
|
|
|
|
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
|
|
|
}
|
|
|
|
return &res.Result, nil
|
|
|
|
}
|
|
|
|
|
2018-09-14 06:48:43 -06:00
|
|
|
// GetBlockWithoutHeader is an optimization - it does not call GetBlockHeader to get prev, next hashes
|
2018-02-28 16:32:40 -07:00
|
|
|
// instead it sets to header only block hash and height passed in parameters
|
2018-06-14 15:12:19 -06:00
|
|
|
func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) {
|
2018-02-28 16:32:40 -07:00
|
|
|
data, err := b.GetBlockRaw(hash)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-03-08 11:39:21 -07:00
|
|
|
block, err := b.Parser.ParseBlock(data)
|
2018-02-28 16:32:40 -07:00
|
|
|
if err != nil {
|
2018-03-01 03:06:10 -07:00
|
|
|
return nil, errors.Annotatef(err, "%v %v", height, hash)
|
2018-02-28 16:32:40 -07:00
|
|
|
}
|
|
|
|
block.BlockHeader.Hash = hash
|
|
|
|
block.BlockHeader.Height = height
|
|
|
|
return block, nil
|
|
|
|
}
|
|
|
|
|
2018-10-31 10:17:22 -06:00
|
|
|
// GetBlockRaw returns block with given hash as bytes
|
2017-09-12 10:08:01 -06:00
|
|
|
func (b *BitcoinRPC) GetBlockRaw(hash string) ([]byte, error) {
|
2018-01-30 10:22:25 -07:00
|
|
|
glog.V(1).Info("rpc: getblock (verbosity=0) ", hash)
|
2017-09-12 10:08:01 -06:00
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResGetBlockRaw{}
|
|
|
|
req := CmdGetBlock{Method: "getblock"}
|
2017-09-12 10:08:01 -06:00
|
|
|
req.Params.BlockHash = hash
|
2017-09-12 16:36:08 -06:00
|
|
|
req.Params.Verbosity = 0
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2017-09-12 10:08:01 -06:00
|
|
|
|
|
|
|
if err != nil {
|
2018-03-01 03:06:10 -07:00
|
|
|
return nil, errors.Annotatef(err, "hash %v", hash)
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
2017-09-12 10:08:01 -06:00
|
|
|
if res.Error != nil {
|
2019-01-17 12:31:15 -07:00
|
|
|
if IsErrBlockNotFound(res.Error) {
|
2018-03-26 07:17:44 -06:00
|
|
|
return nil, bchain.ErrBlockNotFound
|
|
|
|
}
|
2018-03-01 03:06:10 -07:00
|
|
|
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|
|
|
|
return hex.DecodeString(res.Result)
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
|
|
|
|
2018-10-31 10:17:22 -06:00
|
|
|
// GetBlockFull returns block with given hash
|
2018-03-08 10:36:01 -07:00
|
|
|
func (b *BitcoinRPC) GetBlockFull(hash string) (*bchain.Block, error) {
|
2018-01-30 10:22:25 -07:00
|
|
|
glog.V(1).Info("rpc: getblock (verbosity=2) ", hash)
|
2017-09-12 16:36:08 -06:00
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResGetBlockFull{}
|
|
|
|
req := CmdGetBlock{Method: "getblock"}
|
2017-09-12 16:36:08 -06:00
|
|
|
req.Params.BlockHash = hash
|
|
|
|
req.Params.Verbosity = 2
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2017-09-12 16:36:08 -06:00
|
|
|
|
|
|
|
if err != nil {
|
2018-03-01 03:06:10 -07:00
|
|
|
return nil, errors.Annotatef(err, "hash %v", hash)
|
2017-09-12 16:36:08 -06:00
|
|
|
}
|
|
|
|
if res.Error != nil {
|
2019-01-17 12:31:15 -07:00
|
|
|
if IsErrBlockNotFound(res.Error) {
|
2018-03-26 07:17:44 -06:00
|
|
|
return nil, bchain.ErrBlockNotFound
|
|
|
|
}
|
2018-03-01 03:06:10 -07:00
|
|
|
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
2019-01-28 17:39:54 -07:00
|
|
|
|
|
|
|
for i := range res.Result.Txs {
|
|
|
|
tx := &res.Result.Txs[i]
|
|
|
|
for j := range tx.Vout {
|
|
|
|
vout := &tx.Vout[j]
|
|
|
|
// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
|
|
|
|
vout.ValueSat, err = b.Parser.AmountToBigInt(vout.JsonValue)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
vout.JsonValue = ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-12 10:08:01 -06:00
|
|
|
return &res.Result, nil
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
|
|
|
|
2019-03-25 09:43:57 -06:00
|
|
|
// GetMempoolTransactions returns transactions in mempool
|
|
|
|
func (b *BitcoinRPC) GetMempoolTransactions() ([]string, error) {
|
2018-01-31 07:04:54 -07:00
|
|
|
glog.V(1).Info("rpc: getrawmempool")
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResGetMempool{}
|
|
|
|
req := CmdGetMempool{Method: "getrawmempool"}
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2018-01-31 07:04:54 -07:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if res.Error != nil {
|
|
|
|
return nil, res.Error
|
|
|
|
}
|
|
|
|
return res.Result, nil
|
|
|
|
}
|
|
|
|
|
2019-03-25 09:43:57 -06:00
|
|
|
// IsMissingTx return true if error means missing tx
|
2019-01-17 12:31:15 -07:00
|
|
|
func IsMissingTx(err *bchain.RPCError) bool {
|
2019-01-10 08:39:36 -07:00
|
|
|
if err.Code == -5 { // "No such mempool or blockchain transaction"
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-10-31 10:17:22 -06:00
|
|
|
// GetTransactionForMempool returns a transaction by the transaction ID
|
2018-05-14 10:12:01 -06:00
|
|
|
// It could be optimized for mempool, i.e. without block time and confirmations
|
|
|
|
func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
|
|
|
glog.V(1).Info("rpc: getrawtransaction nonverbose ", txid)
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResGetRawTransactionNonverbose{}
|
|
|
|
req := CmdGetRawTransaction{Method: "getrawtransaction"}
|
2018-05-14 10:12:01 -06:00
|
|
|
req.Params.Txid = txid
|
|
|
|
req.Params.Verbose = false
|
|
|
|
err := b.Call(&req, &res)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Annotatef(err, "txid %v", txid)
|
|
|
|
}
|
|
|
|
if res.Error != nil {
|
2019-01-17 12:31:15 -07:00
|
|
|
if IsMissingTx(res.Error) {
|
2019-01-10 08:39:36 -07:00
|
|
|
return nil, bchain.ErrTxNotFound
|
|
|
|
}
|
2018-05-14 10:12:01 -06:00
|
|
|
return nil, errors.Annotatef(res.Error, "txid %v", txid)
|
|
|
|
}
|
|
|
|
data, err := hex.DecodeString(res.Result)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Annotatef(err, "txid %v", txid)
|
|
|
|
}
|
|
|
|
tx, err := b.Parser.ParseTx(data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Annotatef(err, "txid %v", txid)
|
|
|
|
}
|
|
|
|
return tx, nil
|
|
|
|
}
|
|
|
|
|
2018-10-31 10:17:22 -06:00
|
|
|
// GetTransaction returns a transaction by the transaction ID
|
2018-03-08 10:36:01 -07:00
|
|
|
func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
2018-11-13 02:31:27 -07:00
|
|
|
r, err := b.getRawTransaction(txid)
|
2018-10-16 04:25:31 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tx, err := b.Parser.ParseTxFromJson(r)
|
2018-11-13 02:31:27 -07:00
|
|
|
tx.CoinSpecificData = r
|
2018-10-16 04:25:31 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Annotatef(err, "txid %v", txid)
|
|
|
|
}
|
|
|
|
return tx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTransactionSpecific returns json as returned by backend, with all coin specific data
|
2018-11-13 02:31:27 -07:00
|
|
|
func (b *BitcoinRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
|
|
|
|
if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok {
|
|
|
|
return csd, nil
|
|
|
|
}
|
|
|
|
return b.getRawTransaction(tx.Txid)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getRawTransaction returns json as returned by backend, with all coin specific data
|
|
|
|
func (b *BitcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) {
|
2018-01-30 10:22:25 -07:00
|
|
|
glog.V(1).Info("rpc: getrawtransaction ", txid)
|
2017-09-06 02:59:40 -06:00
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResGetRawTransaction{}
|
|
|
|
req := CmdGetRawTransaction{Method: "getrawtransaction"}
|
2017-09-12 10:08:01 -06:00
|
|
|
req.Params.Txid = txid
|
|
|
|
req.Params.Verbose = true
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2017-08-28 09:50:57 -06:00
|
|
|
|
2017-09-12 10:08:01 -06:00
|
|
|
if err != nil {
|
2018-03-01 03:06:10 -07:00
|
|
|
return nil, errors.Annotatef(err, "txid %v", txid)
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
2017-09-12 10:08:01 -06:00
|
|
|
if res.Error != nil {
|
2019-01-17 12:31:15 -07:00
|
|
|
if IsMissingTx(res.Error) {
|
2019-01-10 08:39:36 -07:00
|
|
|
return nil, bchain.ErrTxNotFound
|
|
|
|
}
|
2018-03-01 03:06:10 -07:00
|
|
|
return nil, errors.Annotatef(res.Error, "txid %v", txid)
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
2018-10-16 04:25:31 -06:00
|
|
|
return res.Result, nil
|
2017-08-28 09:50:57 -06:00
|
|
|
}
|
|
|
|
|
2018-08-30 14:39:03 -06:00
|
|
|
// EstimateSmartFee returns fee estimation
|
2018-07-24 07:58:37 -06:00
|
|
|
func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
|
|
|
|
// use EstimateFee if EstimateSmartFee is not supported
|
|
|
|
if !b.ChainConfig.SupportsEstimateSmartFee && b.ChainConfig.SupportsEstimateFee {
|
|
|
|
return b.EstimateFee(blocks)
|
|
|
|
}
|
|
|
|
|
2018-02-07 11:59:09 -07:00
|
|
|
glog.V(1).Info("rpc: estimatesmartfee ", blocks)
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResEstimateSmartFee{}
|
|
|
|
req := CmdEstimateSmartFee{Method: "estimatesmartfee"}
|
2018-02-07 11:59:09 -07:00
|
|
|
req.Params.ConfTarget = blocks
|
|
|
|
if conservative {
|
|
|
|
req.Params.EstimateMode = "CONSERVATIVE"
|
|
|
|
} else {
|
|
|
|
req.Params.EstimateMode = "ECONOMICAL"
|
|
|
|
}
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2018-02-07 11:59:09 -07:00
|
|
|
|
2018-07-24 07:58:37 -06:00
|
|
|
var r big.Int
|
2018-02-07 11:59:09 -07:00
|
|
|
if err != nil {
|
2018-07-24 07:58:37 -06:00
|
|
|
return r, err
|
2018-02-07 11:59:09 -07:00
|
|
|
}
|
|
|
|
if res.Error != nil {
|
2018-07-24 07:58:37 -06:00
|
|
|
return r, res.Error
|
2018-02-07 11:59:09 -07:00
|
|
|
}
|
2018-07-24 07:58:37 -06:00
|
|
|
r, err = b.Parser.AmountToBigInt(res.Result.Feerate)
|
|
|
|
if err != nil {
|
|
|
|
return r, err
|
|
|
|
}
|
|
|
|
return r, nil
|
2018-02-07 11:59:09 -07:00
|
|
|
}
|
|
|
|
|
2018-03-27 02:30:30 -06:00
|
|
|
// EstimateFee returns fee estimation.
|
2018-07-24 07:58:37 -06:00
|
|
|
func (b *BitcoinRPC) EstimateFee(blocks int) (big.Int, error) {
|
|
|
|
// use EstimateSmartFee if EstimateFee is not supported
|
|
|
|
if !b.ChainConfig.SupportsEstimateFee && b.ChainConfig.SupportsEstimateSmartFee {
|
|
|
|
return b.EstimateSmartFee(blocks, true)
|
|
|
|
}
|
|
|
|
|
2018-03-27 02:30:30 -06:00
|
|
|
glog.V(1).Info("rpc: estimatefee ", blocks)
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResEstimateFee{}
|
|
|
|
req := CmdEstimateFee{Method: "estimatefee"}
|
2018-03-27 02:30:30 -06:00
|
|
|
req.Params.Blocks = blocks
|
|
|
|
err := b.Call(&req, &res)
|
|
|
|
|
2018-07-24 07:58:37 -06:00
|
|
|
var r big.Int
|
2018-03-27 02:30:30 -06:00
|
|
|
if err != nil {
|
2018-07-24 07:58:37 -06:00
|
|
|
return r, err
|
2018-03-27 02:30:30 -06:00
|
|
|
}
|
|
|
|
if res.Error != nil {
|
2018-07-24 07:58:37 -06:00
|
|
|
return r, res.Error
|
2018-03-27 02:30:30 -06:00
|
|
|
}
|
2018-07-24 07:58:37 -06:00
|
|
|
r, err = b.Parser.AmountToBigInt(res.Result)
|
|
|
|
if err != nil {
|
|
|
|
return r, err
|
|
|
|
}
|
|
|
|
return r, nil
|
2018-03-27 02:30:30 -06:00
|
|
|
}
|
|
|
|
|
2019-03-25 09:43:57 -06:00
|
|
|
// SendRawTransaction sends raw transaction
|
2018-02-20 04:01:42 -07:00
|
|
|
func (b *BitcoinRPC) SendRawTransaction(tx string) (string, error) {
|
|
|
|
glog.V(1).Info("rpc: sendrawtransaction")
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResSendRawTransaction{}
|
|
|
|
req := CmdSendRawTransaction{Method: "sendrawtransaction"}
|
2018-02-20 04:01:42 -07:00
|
|
|
req.Params = []string{tx}
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2018-02-20 04:01:42 -07:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if res.Error != nil {
|
|
|
|
return "", res.Error
|
|
|
|
}
|
|
|
|
return res.Result, nil
|
|
|
|
}
|
|
|
|
|
2018-03-22 08:43:14 -06:00
|
|
|
// GetMempoolEntry returns mempool data for given transaction
|
2018-03-08 10:36:01 -07:00
|
|
|
func (b *BitcoinRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
|
2018-03-07 04:08:37 -07:00
|
|
|
glog.V(1).Info("rpc: getmempoolentry")
|
|
|
|
|
2018-06-09 06:37:05 -06:00
|
|
|
res := ResGetMempoolEntry{}
|
|
|
|
req := CmdGetMempoolEntry{
|
2018-03-07 04:08:37 -07:00
|
|
|
Method: "getmempoolentry",
|
|
|
|
Params: []string{txid},
|
|
|
|
}
|
2018-03-21 08:33:48 -06:00
|
|
|
err := b.Call(&req, &res)
|
2018-03-07 04:08:37 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if res.Error != nil {
|
|
|
|
return nil, res.Error
|
|
|
|
}
|
2018-07-25 07:56:08 -06:00
|
|
|
res.Result.FeeSat, err = b.Parser.AmountToBigInt(res.Result.Fee)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res.Result.ModifiedFeeSat, err = b.Parser.AmountToBigInt(res.Result.ModifiedFee)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-03-07 04:08:37 -07:00
|
|
|
return res.Result, nil
|
|
|
|
}
|
|
|
|
|
2018-06-13 03:58:25 -06:00
|
|
|
func safeDecodeResponse(body io.ReadCloser, res interface{}) (err error) {
|
|
|
|
var data []byte
|
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data))
|
2018-11-28 06:27:02 -07:00
|
|
|
debug.PrintStack()
|
2018-06-13 03:58:25 -06:00
|
|
|
if len(data) > 0 && len(data) < 2048 {
|
2018-10-04 01:19:41 -06:00
|
|
|
err = errors.Errorf("Error: %v", string(data))
|
2018-06-13 03:58:25 -06:00
|
|
|
} else {
|
|
|
|
err = errors.New("Internal error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
data, err = ioutil.ReadAll(body)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return json.Unmarshal(data, &res)
|
|
|
|
}
|
|
|
|
|
2019-01-24 07:24:56 -07:00
|
|
|
// Call calls Backend RPC interface, using RPCMarshaler interface to marshall the request
|
2018-03-21 08:33:48 -06:00
|
|
|
func (b *BitcoinRPC) Call(req interface{}, res interface{}) error {
|
2018-06-09 06:37:05 -06:00
|
|
|
httpData, err := b.RPCMarshaler.Marshal(req)
|
2017-09-12 10:08:01 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-08 04:59:37 -07:00
|
|
|
httpReq, err := http.NewRequest("POST", b.rpcURL, bytes.NewBuffer(httpData))
|
2017-09-12 10:08:01 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-08 04:59:37 -07:00
|
|
|
httpReq.SetBasicAuth(b.user, b.password)
|
2017-09-12 10:08:01 -06:00
|
|
|
httpRes, err := b.client.Do(httpReq)
|
2018-03-02 05:49:32 -07:00
|
|
|
// in some cases the httpRes can contain data even if it returns error
|
|
|
|
// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
|
|
|
|
if httpRes != nil {
|
|
|
|
defer httpRes.Body.Close()
|
|
|
|
}
|
2017-09-12 10:08:01 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-10 17:31:09 -07:00
|
|
|
// if server returns HTTP error code it might not return json with response
|
|
|
|
// handle both cases
|
|
|
|
if httpRes.StatusCode != 200 {
|
2018-06-13 03:58:25 -06:00
|
|
|
err = safeDecodeResponse(httpRes.Body, &res)
|
2018-03-10 17:31:09 -07:00
|
|
|
if err != nil {
|
2018-06-13 03:58:25 -06:00
|
|
|
return errors.Errorf("%v %v", httpRes.Status, err)
|
2018-03-10 17:31:09 -07:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2018-06-13 03:58:25 -06:00
|
|
|
return safeDecodeResponse(httpRes.Body, &res)
|
2017-09-12 10:08:01 -06:00
|
|
|
}
|