github.com/cerberus-wallet/blockbook@v0.3.2/bchain/coins/nuls/nulsrpc.go (about)

     1  package nuls
     2  
     3  import (
     4  	"blockbook/bchain"
     5  	"blockbook/bchain/coins/btc"
     6  	"bytes"
     7  	"encoding/base64"
     8  	"encoding/hex"
     9  	"encoding/json"
    10  	"io"
    11  	"io/ioutil"
    12  	"math/big"
    13  	"net"
    14  	"net/http"
    15  	"runtime/debug"
    16  	"strconv"
    17  	"time"
    18  
    19  	"github.com/juju/errors"
    20  
    21  	"github.com/golang/glog"
    22  )
    23  
    24  // NulsRPC is an interface to JSON-RPC bitcoind service
    25  type NulsRPC struct {
    26  	*btc.BitcoinRPC
    27  	client   http.Client
    28  	rpcURL   string
    29  	user     string
    30  	password string
    31  }
    32  
    33  // NewNulsRPC returns new NulsRPC instance
    34  func NewNulsRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
    35  	b, err := btc.NewBitcoinRPC(config, pushHandler)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	var c btc.Configuration
    41  	err = json.Unmarshal(config, &c)
    42  	if err != nil {
    43  		return nil, errors.Annotatef(err, "Invalid configuration file")
    44  	}
    45  
    46  	transport := &http.Transport{
    47  		Dial:                (&net.Dialer{KeepAlive: 600 * time.Second}).Dial,
    48  		MaxIdleConns:        100,
    49  		MaxIdleConnsPerHost: 100, // necessary to not to deplete ports
    50  	}
    51  
    52  	s := &NulsRPC{
    53  		BitcoinRPC: b.(*btc.BitcoinRPC),
    54  		client:     http.Client{Timeout: time.Duration(c.RPCTimeout) * time.Second, Transport: transport},
    55  		rpcURL:     c.RPCURL,
    56  		user:       c.RPCUser,
    57  		password:   c.RPCPass,
    58  	}
    59  	s.BitcoinRPC.RPCMarshaler = btc.JSONMarshalerV1{}
    60  	s.BitcoinRPC.ChainConfig.SupportsEstimateSmartFee = false
    61  
    62  	return s, nil
    63  }
    64  
    65  // Initialize initializes GincoinRPC instance.
    66  func (n *NulsRPC) Initialize() error {
    67  	chainName := ""
    68  	params := GetChainParams(chainName)
    69  
    70  	// always create parser
    71  	n.BitcoinRPC.Parser = NewNulsParser(params, n.BitcoinRPC.ChainConfig)
    72  
    73  	// parameters for getInfo request
    74  	if params.Net == MainnetMagic {
    75  		n.BitcoinRPC.Testnet = false
    76  		n.BitcoinRPC.Network = "livenet"
    77  	} else {
    78  		n.BitcoinRPC.Testnet = true
    79  		n.BitcoinRPC.Network = "testnet"
    80  	}
    81  
    82  	glog.Info("rpc: block chain ", params.Name)
    83  
    84  	return nil
    85  }
    86  
    87  type CmdGetNetworkInfo struct {
    88  	Success bool `json:"success"`
    89  	Data    struct {
    90  		LocalBestHeight int64  `json:"localBestHeight"`
    91  		NetBestHeight   int    `json:"netBestHeight"`
    92  		TimeOffset      string `json:"timeOffset"`
    93  		InCount         int8   `json:"inCount"`
    94  		OutCount        int8   `json:"outCount"`
    95  	} `json:"data"`
    96  }
    97  
    98  type CmdGetVersionInfo struct {
    99  	Success bool `json:"success"`
   100  	Data    struct {
   101  		MyVersion      string `json:"myVersion"`
   102  		NewestVersion  string `json:"newestVersion"`
   103  		NetworkVersion int    `json:"networkVersion"`
   104  		Information    string `json:"information"`
   105  	} `json:"data"`
   106  }
   107  
   108  type CmdGetBestBlockHash struct {
   109  	Success bool `json:"success"`
   110  	Data    struct {
   111  		Value string `json:"value"`
   112  	} `json:"data"`
   113  }
   114  
   115  type CmdGetBestBlockHeight struct {
   116  	Success bool `json:"success"`
   117  	Data    struct {
   118  		Value uint32 `json:"value"`
   119  	} `json:"data"`
   120  }
   121  
   122  type CmdTxBroadcast struct {
   123  	Success bool `json:"success"`
   124  	Data    struct {
   125  		Value string `json:"value"`
   126  	} `json:"data"`
   127  }
   128  
   129  type CmdGetBlockHeader struct {
   130  	Success bool `json:"success"`
   131  	Data    struct {
   132  		Hash           string  `json:"hash"`
   133  		PreHash        string  `json:"preHash"`
   134  		MerkleHash     string  `json:"merkleHash"`
   135  		StateRoot      string  `json:"stateRoot"`
   136  		Time           int64   `json:"time"`
   137  		Height         int64   `json:"height"`
   138  		TxCount        int     `json:"txCount"`
   139  		PackingAddress string  `json:"packingAddress"`
   140  		ConfirmCount   int     `json:"confirmCount"`
   141  		ScriptSig      string  `json:"scriptSig"`
   142  		Size           int     `json:"size"`
   143  		Reward         float64 `json:"reward"`
   144  		Fee            float64 `json:"fee"`
   145  	} `json:"data"`
   146  }
   147  
   148  type CmdGetBlock struct {
   149  	Success bool `json:"success"`
   150  	Data    struct {
   151  		Hash           string  `json:"hash"`
   152  		PreHash        string  `json:"preHash"`
   153  		MerkleHash     string  `json:"merkleHash"`
   154  		StateRoot      string  `json:"stateRoot"`
   155  		Time           int64   `json:"time"`
   156  		Height         int64   `json:"height"`
   157  		TxCount        int     `json:"txCount"`
   158  		PackingAddress string  `json:"packingAddress"`
   159  		ConfirmCount   int     `json:"confirmCount"`
   160  		ScriptSig      string  `json:"scriptSig"`
   161  		Size           int     `json:"size"`
   162  		Reward         float64 `json:"reward"`
   163  		Fee            float64 `json:"fee"`
   164  
   165  		TxList []Tx `json:"txList"`
   166  	} `json:"data"`
   167  }
   168  
   169  type CmdGetTx struct {
   170  	Success bool `json:"success"`
   171  	Tx      Tx   `json:"data"`
   172  }
   173  
   174  type Tx struct {
   175  	Hash         string  `json:"hash"`
   176  	Type         int     `json:"type"`
   177  	Time         int64   `json:"time"`
   178  	BlockHeight  int64   `json:"blockHeight"`
   179  	Fee          float64 `json:"fee"`
   180  	Value        float64 `json:"value"`
   181  	Remark       string  `json:"remark"`
   182  	ScriptSig    string  `json:"scriptSig"`
   183  	Status       int     `json:"status"`
   184  	ConfirmCount int     `json:"confirmCount"`
   185  	Size         int     `json:"size"`
   186  	Inputs       []struct {
   187  		FromHash  string  `json:"fromHash"`
   188  		FromIndex uint32  `json:"fromIndex"`
   189  		Address   string  `json:"address"`
   190  		Value     float64 `json:"value"`
   191  	} `json:"inputs"`
   192  	Outputs []struct {
   193  		Address  string `json:"address"`
   194  		Value    int64  `json:"value"`
   195  		LockTime int64  `json:"lockTime"`
   196  	} `json:"outputs"`
   197  }
   198  
   199  type CmdGetTxBytes struct {
   200  	Success bool `json:"success"`
   201  	Data    struct {
   202  		Value string `json:"value"`
   203  	} `json:"data"`
   204  }
   205  
   206  func (n *NulsRPC) GetChainInfo() (*bchain.ChainInfo, error) {
   207  	networkInfo := CmdGetNetworkInfo{}
   208  	error := n.Call("/api/network/info", &networkInfo)
   209  	if error != nil {
   210  		return nil, error
   211  	}
   212  
   213  	versionInfo := CmdGetVersionInfo{}
   214  	error = n.Call("/api/client/version", &versionInfo)
   215  	if error != nil {
   216  		return nil, error
   217  	}
   218  
   219  	chainInfo := &bchain.ChainInfo{
   220  		Chain:           "nuls",
   221  		Blocks:          networkInfo.Data.NetBestHeight,
   222  		Headers:         networkInfo.Data.NetBestHeight,
   223  		Bestblockhash:   "",
   224  		Difficulty:      networkInfo.Data.TimeOffset,
   225  		SizeOnDisk:      networkInfo.Data.LocalBestHeight,
   226  		Version:         versionInfo.Data.MyVersion,
   227  		Subversion:      versionInfo.Data.NewestVersion,
   228  		ProtocolVersion: strconv.Itoa(versionInfo.Data.NetworkVersion),
   229  		Timeoffset:      0,
   230  		Warnings:        versionInfo.Data.Information,
   231  	}
   232  	return chainInfo, nil
   233  }
   234  
   235  func (n *NulsRPC) GetBestBlockHash() (string, error) {
   236  	bestBlockHash := CmdGetBestBlockHash{}
   237  	error := n.Call("/api/block/newest/hash", &bestBlockHash)
   238  	if error != nil {
   239  		return "", error
   240  	}
   241  	return bestBlockHash.Data.Value, nil
   242  }
   243  
   244  func (n *NulsRPC) GetBestBlockHeight() (uint32, error) {
   245  	bestBlockHeight := CmdGetBestBlockHeight{}
   246  	error := n.Call("/api/block/newest/height", &bestBlockHeight)
   247  	if error != nil {
   248  		return 0, error
   249  	}
   250  	return bestBlockHeight.Data.Value, nil
   251  }
   252  
   253  func (n *NulsRPC) GetBlockHash(height uint32) (string, error) {
   254  	blockHeader := CmdGetBlockHeader{}
   255  	error := n.Call("/api/block/header/height/"+strconv.Itoa(int(height)), &blockHeader)
   256  	if error != nil {
   257  		return "", error
   258  	}
   259  
   260  	if !blockHeader.Success {
   261  		return "", bchain.ErrBlockNotFound
   262  	}
   263  
   264  	return blockHeader.Data.Hash, nil
   265  }
   266  
   267  func (n *NulsRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
   268  	uri := "/api/block/header/hash/" + hash
   269  	return n.getBlobkHeader(uri)
   270  }
   271  
   272  func (n *NulsRPC) GetBlockHeaderByHeight(height uint32) (*bchain.BlockHeader, error) {
   273  	uri := "/api/block/header/height/" + strconv.Itoa(int(height))
   274  	return n.getBlobkHeader(uri)
   275  }
   276  
   277  func (n *NulsRPC) getBlobkHeader(uri string) (*bchain.BlockHeader, error) {
   278  	blockHeader := CmdGetBlockHeader{}
   279  	error := n.Call(uri, &blockHeader)
   280  	if error != nil {
   281  		return nil, error
   282  	}
   283  
   284  	if !blockHeader.Success {
   285  		return nil, bchain.ErrBlockNotFound
   286  	}
   287  
   288  	nexHash, _ := n.GetBlockHash(uint32(blockHeader.Data.Height + 1))
   289  
   290  	header := &bchain.BlockHeader{
   291  		Hash:          blockHeader.Data.Hash,
   292  		Prev:          blockHeader.Data.PreHash,
   293  		Next:          nexHash,
   294  		Height:        uint32(blockHeader.Data.Height),
   295  		Confirmations: blockHeader.Data.ConfirmCount,
   296  		Size:          blockHeader.Data.Size,
   297  		Time:          blockHeader.Data.Time / 1000,
   298  	}
   299  	return header, nil
   300  }
   301  
   302  func (n *NulsRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
   303  
   304  	url := "/api/block/hash/" + hash
   305  
   306  	if hash == "" {
   307  		url = "/api/block/height/" + strconv.Itoa(int(height))
   308  	}
   309  
   310  	getBlock := CmdGetBlock{}
   311  	error := n.Call(url, &getBlock)
   312  	if error != nil {
   313  		return nil, error
   314  	}
   315  
   316  	if !getBlock.Success {
   317  		return nil, bchain.ErrBlockNotFound
   318  	}
   319  
   320  	nexHash, _ := n.GetBlockHash(uint32(getBlock.Data.Height + 1))
   321  
   322  	header := bchain.BlockHeader{
   323  		Hash:          getBlock.Data.Hash,
   324  		Prev:          getBlock.Data.PreHash,
   325  		Next:          nexHash,
   326  		Height:        uint32(getBlock.Data.Height),
   327  		Confirmations: getBlock.Data.ConfirmCount,
   328  		Size:          getBlock.Data.Size,
   329  		Time:          getBlock.Data.Time / 1000,
   330  	}
   331  
   332  	var txs []bchain.Tx
   333  
   334  	for _, rawTx := range getBlock.Data.TxList {
   335  		tx, err := converTx(rawTx)
   336  		if err != nil {
   337  			return nil, err
   338  		}
   339  		tx.Blocktime = header.Time
   340  		txs = append(txs, *tx)
   341  	}
   342  
   343  	block := &bchain.Block{
   344  		BlockHeader: header,
   345  		Txs:         txs,
   346  	}
   347  	return block, nil
   348  }
   349  
   350  func (n *NulsRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
   351  	if hash == "" {
   352  		return nil, bchain.ErrBlockNotFound
   353  	}
   354  
   355  	getBlock := CmdGetBlock{}
   356  	error := n.Call("/api/block/hash/"+hash, &getBlock)
   357  	if error != nil {
   358  		return nil, error
   359  	}
   360  
   361  	if !getBlock.Success {
   362  		return nil, bchain.ErrBlockNotFound
   363  	}
   364  
   365  	nexHash, _ := n.GetBlockHash(uint32(getBlock.Data.Height + 1))
   366  
   367  	header := bchain.BlockHeader{
   368  		Hash:          getBlock.Data.Hash,
   369  		Prev:          getBlock.Data.PreHash,
   370  		Next:          nexHash,
   371  		Height:        uint32(getBlock.Data.Height),
   372  		Confirmations: getBlock.Data.ConfirmCount,
   373  		Size:          getBlock.Data.Size,
   374  		Time:          getBlock.Data.Time / 1000,
   375  	}
   376  
   377  	var txIds []string
   378  
   379  	for _, rawTx := range getBlock.Data.TxList {
   380  		txIds = append(txIds, rawTx.Hash)
   381  	}
   382  
   383  	blockInfo := &bchain.BlockInfo{
   384  		BlockHeader: header,
   385  		MerkleRoot:  getBlock.Data.MerkleHash,
   386  		//Version:	getBlock.Data.StateRoot,
   387  		Txids: txIds,
   388  	}
   389  	return blockInfo, nil
   390  }
   391  
   392  func (n *NulsRPC) GetMempoolTransactions() ([]string, error) {
   393  	return nil, nil
   394  }
   395  
   396  func (n *NulsRPC) GetTransaction(txid string) (*bchain.Tx, error) {
   397  	if txid == "" {
   398  		return nil, bchain.ErrTxidMissing
   399  	}
   400  
   401  	getTx := CmdGetTx{}
   402  	error := n.Call("/api/tx/hash/"+txid, &getTx)
   403  	if error != nil {
   404  		return nil, error
   405  	}
   406  
   407  	if !getTx.Success {
   408  		return nil, bchain.ErrTxNotFound
   409  	}
   410  
   411  	tx, err := converTx(getTx.Tx)
   412  	if err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	blockHeaderHeight := getTx.Tx.BlockHeight
   417  	// shouldn't it check the error here?
   418  	blockHeader, _ := n.GetBlockHeaderByHeight(uint32(blockHeaderHeight))
   419  	if blockHeader != nil {
   420  		tx.Blocktime = blockHeader.Time
   421  	}
   422  
   423  	hexBytys, e := n.GetTransactionSpecific(tx)
   424  	if e == nil {
   425  		var hex string
   426  		json.Unmarshal(hexBytys, &hex)
   427  		tx.Hex = hex
   428  		tx.CoinSpecificData = hex
   429  	}
   430  	return tx, nil
   431  }
   432  
   433  func (n *NulsRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
   434  	return nil, nil
   435  }
   436  
   437  func (n *NulsRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
   438  	if tx == nil {
   439  		return nil, bchain.ErrTxNotFound
   440  	}
   441  
   442  	if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok {
   443  		return csd, nil
   444  	}
   445  
   446  	getTxBytes := CmdGetTxBytes{}
   447  	error := n.Call("/api/tx/bytes?hash="+tx.Txid, &getTxBytes)
   448  	if error != nil {
   449  		return nil, error
   450  	}
   451  
   452  	if !getTxBytes.Success {
   453  		return nil, bchain.ErrTxNotFound
   454  	}
   455  
   456  	txBytes, byErr := base64.StdEncoding.DecodeString(getTxBytes.Data.Value)
   457  	if byErr != nil {
   458  		return nil, byErr
   459  	}
   460  	hexBytes := make([]byte, len(txBytes)*2)
   461  	hex.Encode(hexBytes, txBytes)
   462  
   463  	m, err := json.Marshal(string(hexBytes))
   464  	return json.RawMessage(m), err
   465  }
   466  
   467  func (n *NulsRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
   468  	return n.EstimateFee(blocks)
   469  }
   470  
   471  func (n *NulsRPC) EstimateFee(blocks int) (big.Int, error) {
   472  	return *big.NewInt(100000), nil
   473  }
   474  
   475  func (n *NulsRPC) SendRawTransaction(tx string) (string, error) {
   476  	broadcast := CmdTxBroadcast{}
   477  	req := struct {
   478  		TxHex string `json:"txHex"`
   479  	}{
   480  		TxHex: tx,
   481  	}
   482  
   483  	error := n.Post("/api/accountledger/transaction/broadcast", req, &broadcast)
   484  	if error != nil {
   485  		return "", error
   486  	}
   487  
   488  	if !broadcast.Success {
   489  		return "", bchain.ErrTxidMissing
   490  	}
   491  
   492  	return broadcast.Data.Value, nil
   493  }
   494  
   495  // Call calls Backend RPC interface, using RPCMarshaler interface to marshall the request
   496  func (b *NulsRPC) Call(uri string, res interface{}) error {
   497  	url := b.rpcURL + uri
   498  	httpReq, err := http.NewRequest("GET", url, nil)
   499  	if err != nil {
   500  		return err
   501  	}
   502  	httpReq.SetBasicAuth(b.user, b.password)
   503  	httpRes, err := b.client.Do(httpReq)
   504  	// in some cases the httpRes can contain data even if it returns error
   505  	// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
   506  	if httpRes != nil {
   507  		defer httpRes.Body.Close()
   508  	}
   509  	if err != nil {
   510  		return err
   511  	}
   512  	// if server returns HTTP error code it might not return json with response
   513  	// handle both cases
   514  	if httpRes.StatusCode != 200 {
   515  		err = safeDecodeResponse(httpRes.Body, &res)
   516  		if err != nil {
   517  			return errors.Errorf("%v %v", httpRes.Status, err)
   518  		}
   519  		return nil
   520  	}
   521  	return safeDecodeResponse(httpRes.Body, &res)
   522  }
   523  
   524  func (b *NulsRPC) Post(uri string, req interface{}, res interface{}) error {
   525  	url := b.rpcURL + uri
   526  	httpData, err := b.RPCMarshaler.Marshal(req)
   527  	if err != nil {
   528  		return err
   529  	}
   530  	httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(httpData))
   531  	if err != nil {
   532  		return err
   533  	}
   534  	httpReq.SetBasicAuth(b.user, b.password)
   535  	httpReq.Header.Set("Content-Type", "application/json")
   536  	httpRes, err := b.client.Do(httpReq)
   537  	// in some cases the httpRes can contain data even if it returns error
   538  	// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
   539  	if httpRes != nil {
   540  		defer httpRes.Body.Close()
   541  	}
   542  	if err != nil {
   543  		return err
   544  	}
   545  	// if server returns HTTP error code it might not return json with response
   546  	// handle both cases
   547  	if httpRes.StatusCode != 200 {
   548  		err = safeDecodeResponse(httpRes.Body, &res)
   549  		if err != nil {
   550  			return errors.Errorf("%v %v", httpRes.Status, err)
   551  		}
   552  		return nil
   553  	}
   554  	return safeDecodeResponse(httpRes.Body, &res)
   555  }
   556  
   557  func safeDecodeResponse(body io.ReadCloser, res *interface{}) (err error) {
   558  	var data []byte
   559  	defer func() {
   560  		if r := recover(); r != nil {
   561  			glog.Error("unmarshal json recovered from panic: ", r, "; data: ", string(data))
   562  			debug.PrintStack()
   563  			if len(data) > 0 && len(data) < 2048 {
   564  				err = errors.Errorf("Error: %v", string(data))
   565  			} else {
   566  				err = errors.New("Internal error")
   567  			}
   568  		}
   569  	}()
   570  	data, err = ioutil.ReadAll(body)
   571  	if err != nil {
   572  		return err
   573  	}
   574  
   575  	//fmt.Println(string(data))
   576  	error := json.Unmarshal(data, res)
   577  	return error
   578  }
   579  
   580  func converTx(rawTx Tx) (*bchain.Tx, error) {
   581  	var lockTime int64 = 0
   582  	var vins = make([]bchain.Vin, 0)
   583  	var vouts []bchain.Vout
   584  
   585  	for _, input := range rawTx.Inputs {
   586  		vin := bchain.Vin{
   587  			Coinbase:  "",
   588  			Txid:      input.FromHash,
   589  			Vout:      input.FromIndex,
   590  			ScriptSig: bchain.ScriptSig{},
   591  			Sequence:  0,
   592  			Addresses: []string{input.Address},
   593  		}
   594  		vins = append(vins, vin)
   595  	}
   596  
   597  	for index, output := range rawTx.Outputs {
   598  		vout := bchain.Vout{
   599  			ValueSat: *big.NewInt(output.Value),
   600  			//JsonValue: 	"",
   601  			//LockTime:	output.LockTime,
   602  			N: uint32(index),
   603  			ScriptPubKey: bchain.ScriptPubKey{
   604  				Hex: output.Address,
   605  				Addresses: []string{
   606  					output.Address,
   607  				},
   608  			},
   609  		}
   610  		vouts = append(vouts, vout)
   611  
   612  		if lockTime < output.LockTime {
   613  			lockTime = output.LockTime
   614  		}
   615  	}
   616  
   617  	tx := &bchain.Tx{
   618  		Hex:           "",
   619  		Txid:          rawTx.Hash,
   620  		Version:       0,
   621  		LockTime:      uint32(lockTime),
   622  		Vin:           vins,
   623  		Vout:          vouts,
   624  		Confirmations: uint32(rawTx.ConfirmCount),
   625  		Time:          rawTx.Time / 1000,
   626  	}
   627  
   628  	return tx, nil
   629  }