github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/coins/nuls/nulsrpc.go (about)

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