github.com/cryptohub-digital/blockbook-fork@v0.0.0-20230713133354-673c927af7f1/bchain/coins/xcb/xcbrpc.go (about)

     1  package xcb
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"math/big"
     8  	"strconv"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/core-coin/go-core/v2"
    13  	xcbcommon "github.com/core-coin/go-core/v2/common"
    14  	"github.com/core-coin/go-core/v2/common/hexutil"
    15  	"github.com/core-coin/go-core/v2/core/types"
    16  	"github.com/core-coin/go-core/v2/rpc"
    17  	"github.com/core-coin/go-core/v2/xcbclient"
    18  	"github.com/cryptohub-digital/blockbook-fork/bchain"
    19  	"github.com/cryptohub-digital/blockbook-fork/common"
    20  	"github.com/golang/glog"
    21  	"github.com/juju/errors"
    22  )
    23  
    24  // Network type specifies the type of core-coin network
    25  type Network uint32
    26  
    27  const (
    28  	// MainNet is production network
    29  	MainNet Network = 1
    30  	// TestNet is Devin test network
    31  	TestNet Network = 3
    32  )
    33  
    34  // Configuration represents json config file
    35  type Configuration struct {
    36  	CoinName                        string `json:"coin_name"`
    37  	CoinShortcut                    string `json:"coin_shortcut"`
    38  	RPCURL                          string `json:"rpc_url"`
    39  	RPCTimeout                      int    `json:"rpc_timeout"`
    40  	BlockAddressesToKeep            int    `json:"block_addresses_to_keep"`
    41  	AddressAliases                  bool   `json:"address_aliases,omitempty"`
    42  	MempoolTxTimeoutHours           int    `json:"mempoolTxTimeoutHours"`
    43  	QueryBackendOnMempoolResync     bool   `json:"queryBackendOnMempoolResync"`
    44  	ProcessInternalTransactions     bool   `json:"processInternalTransactions"`
    45  	ProcessZeroInternalTransactions bool   `json:"processZeroInternalTransactions"`
    46  	ConsensusNodeVersionURL         string `json:"consensusNodeVersion"`
    47  }
    48  
    49  // CoreblockchainRPC is an interface to JSON-RPC xcb service.
    50  type CoreblockchainRPC struct {
    51  	*bchain.BaseChain
    52  	Client               CVMClient
    53  	RPC                  CVMRPCClient
    54  	MainNetChainID       Network
    55  	Timeout              time.Duration
    56  	Parser               *CoreCoinParser
    57  	PushHandler          func(bchain.NotificationType)
    58  	OpenRPC              func(string) (CVMRPCClient, CVMClient, error)
    59  	Mempool              *bchain.MempoolCoreCoinType
    60  	mempoolInitialized   bool
    61  	bestHeaderLock       sync.Mutex
    62  	bestHeader           CVMHeader
    63  	bestHeaderTime       time.Time
    64  	NewBlock             CVMNewBlockSubscriber
    65  	newBlockSubscription CVMClientSubscription
    66  	NewTx                CVMNewTxSubscriber
    67  	newTxSubscription    CVMClientSubscription
    68  	ChainConfig          *Configuration
    69  }
    70  
    71  // ProcessInternalTransactions specifies if internal transactions are processed
    72  var ProcessInternalTransactions bool
    73  
    74  // NewCoreblockchainRPC returns new XcbRPC instance.
    75  func NewCoreblockchainRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
    76  	var err error
    77  	var c Configuration
    78  	err = json.Unmarshal(config, &c)
    79  	if err != nil {
    80  		return nil, errors.Annotatef(err, "Invalid configuration file")
    81  	}
    82  	// keep at least 100 mappings block->addresses to allow rollback
    83  	if c.BlockAddressesToKeep < 100 {
    84  		c.BlockAddressesToKeep = 100
    85  	}
    86  
    87  	s := &CoreblockchainRPC{
    88  		BaseChain:   &bchain.BaseChain{},
    89  		ChainConfig: &c,
    90  	}
    91  
    92  	ProcessInternalTransactions = c.ProcessInternalTransactions
    93  
    94  	// overwrite TokenTypeMap with crc specific token type names
    95  	bchain.TokenTypeMap = []bchain.TokenTypeName{CRC20TokenType, CRC721TokenType, bchain.ERC1155TokenType}
    96  
    97  	// always create parser
    98  	s.Parser = NewCoreCoinParser(c.BlockAddressesToKeep)
    99  	s.Timeout = time.Duration(c.RPCTimeout) * time.Second
   100  	s.PushHandler = pushHandler
   101  
   102  	return s, nil
   103  }
   104  
   105  // Initialize initializes core coin rpc interface
   106  func (b *CoreblockchainRPC) Initialize() error {
   107  	b.OpenRPC = func(url string) (CVMRPCClient, CVMClient, error) {
   108  		r, err := rpc.Dial(url)
   109  		if err != nil {
   110  			return nil, nil, err
   111  		}
   112  		rc := &CoreCoinRPCClient{Client: r}
   113  		ec := &CoreblockchainClient{Client: xcbclient.NewClient(r)}
   114  		return rc, ec, nil
   115  	}
   116  
   117  	rc, ec, err := b.OpenRPC(b.ChainConfig.RPCURL)
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	// set chain specific
   123  	b.Client = ec
   124  	b.RPC = rc
   125  	b.MainNetChainID = MainNet
   126  	b.NewBlock = &CoreCoinNewBlock{channel: make(chan *types.Header)}
   127  	b.NewTx = &CoreCoinNewTx{channel: make(chan xcbcommon.Hash)}
   128  
   129  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   130  	defer cancel()
   131  
   132  	id, err := b.Client.NetworkID(ctx)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	xcbcommon.DefaultNetworkID = xcbcommon.NetworkID(id.Int64())
   137  	// parameters for getInfo request
   138  	switch Network(id.Uint64()) {
   139  	case MainNet:
   140  		b.Testnet = false
   141  		b.Network = "mainnet"
   142  	case TestNet:
   143  		b.Testnet = true
   144  		b.Network = "devin"
   145  	default:
   146  		return errors.Errorf("Unknown network id %v", id)
   147  	}
   148  	glog.Info("rpc: block chain ", b.Network)
   149  
   150  	return nil
   151  }
   152  
   153  // CreateMempool creates mempool if not already created, however does not initialize it
   154  func (b *CoreblockchainRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) {
   155  	if b.Mempool == nil {
   156  		b.Mempool = bchain.NewMempoolCoreCoinType(chain, b.ChainConfig.MempoolTxTimeoutHours, b.ChainConfig.QueryBackendOnMempoolResync)
   157  		glog.Info("mempool created, MempoolTxTimeoutHours=", b.ChainConfig.MempoolTxTimeoutHours, ", QueryBackendOnMempoolResync=", b.ChainConfig.QueryBackendOnMempoolResync)
   158  	}
   159  	return b.Mempool, nil
   160  }
   161  
   162  // InitializeMempool creates subscriptions to newHeads and newPendingTransactions
   163  func (b *CoreblockchainRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error {
   164  	if b.Mempool == nil {
   165  		return errors.New("Mempool not created")
   166  	}
   167  
   168  	// get initial mempool transactions
   169  	txs, err := b.GetMempoolTransactions()
   170  	if err != nil {
   171  		return err
   172  	}
   173  	for _, txid := range txs {
   174  		b.Mempool.AddTransactionToMempool(txid)
   175  	}
   176  
   177  	b.Mempool.OnNewTxAddr = onNewTxAddr
   178  	b.Mempool.OnNewTx = onNewTx
   179  
   180  	if err = b.subscribeEvents(); err != nil {
   181  		return err
   182  	}
   183  
   184  	b.mempoolInitialized = true
   185  
   186  	return nil
   187  }
   188  
   189  func (b *CoreblockchainRPC) subscribeEvents() error {
   190  	// new block notifications handling
   191  	go func() {
   192  		for {
   193  			h, ok := b.NewBlock.Read()
   194  			if !ok {
   195  				break
   196  			}
   197  			b.UpdateBestHeader(h)
   198  			// notify blockbook
   199  			b.PushHandler(bchain.NotificationNewBlock)
   200  		}
   201  	}()
   202  
   203  	// new block subscription
   204  	if err := b.subscribe(func() (CVMClientSubscription, error) {
   205  		// invalidate the previous subscription - it is either the first one or there was an error
   206  		b.newBlockSubscription = nil
   207  		ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   208  		defer cancel()
   209  		sub, err := b.RPC.XcbSubscribe(ctx, b.NewBlock.Channel(), "newHeads")
   210  		if err != nil {
   211  			return nil, errors.Annotatef(err, "XcbSubscribe newHeads")
   212  		}
   213  		b.newBlockSubscription = sub
   214  		glog.Info("Subscribed to newHeads")
   215  		return sub, nil
   216  	}); err != nil {
   217  		return err
   218  	}
   219  
   220  	// new mempool transaction notifications handling
   221  	go func() {
   222  		for {
   223  			t, ok := b.NewTx.Read()
   224  			if !ok {
   225  				break
   226  			}
   227  			hex := t.Hex()
   228  			if glog.V(2) {
   229  				glog.Info("rpc: new tx ", hex)
   230  			}
   231  			b.Mempool.AddTransactionToMempool(hex)
   232  			b.PushHandler(bchain.NotificationNewTx)
   233  		}
   234  	}()
   235  
   236  	// new mempool transaction subscription
   237  	if err := b.subscribe(func() (CVMClientSubscription, error) {
   238  		// invalidate the previous subscription - it is either the first one or there was an error
   239  		b.newTxSubscription = nil
   240  		ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   241  		defer cancel()
   242  		sub, err := b.RPC.XcbSubscribe(ctx, b.NewTx.Channel(), "newPendingTransactions")
   243  		if err != nil {
   244  			return nil, errors.Annotatef(err, "XcbSubscribe newPendingTransactions")
   245  		}
   246  		b.newTxSubscription = sub
   247  		glog.Info("Subscribed to newPendingTransactions")
   248  		return sub, nil
   249  	}); err != nil {
   250  		return err
   251  	}
   252  
   253  	return nil
   254  }
   255  
   256  // subscribe subscribes notification and tries to resubscribe in case of error
   257  func (b *CoreblockchainRPC) subscribe(f func() (CVMClientSubscription, error)) error {
   258  	s, err := f()
   259  	if err != nil {
   260  		return err
   261  	}
   262  	go func() {
   263  	Loop:
   264  		for {
   265  			// wait for error in subscription
   266  			e := <-s.Err()
   267  			// nil error means sub.Unsubscribe called, exit goroutine
   268  			if e == nil {
   269  				return
   270  			}
   271  			glog.Error("Subscription error ", e)
   272  			timer := time.NewTimer(time.Second * 2)
   273  			// try in 2 second interval to resubscribe
   274  			for {
   275  				select {
   276  				case e = <-s.Err():
   277  					if e == nil {
   278  						return
   279  					}
   280  				case <-timer.C:
   281  					ns, err := f()
   282  					if err == nil {
   283  						// subscription successful, restart wait for next error
   284  						s = ns
   285  						continue Loop
   286  					}
   287  					glog.Error("Resubscribe error ", err)
   288  					timer.Reset(time.Second * 2)
   289  				}
   290  			}
   291  		}
   292  	}()
   293  	return nil
   294  }
   295  
   296  func (b *CoreblockchainRPC) closeRPC() {
   297  	if b.newBlockSubscription != nil {
   298  		b.newBlockSubscription.Unsubscribe()
   299  	}
   300  	if b.newTxSubscription != nil {
   301  		b.newTxSubscription.Unsubscribe()
   302  	}
   303  	if b.RPC != nil {
   304  		b.RPC.Close()
   305  	}
   306  }
   307  
   308  func (b *CoreblockchainRPC) reconnectRPC() error {
   309  	glog.Info("Reconnecting RPC")
   310  	b.closeRPC()
   311  	rc, ec, err := b.OpenRPC(b.ChainConfig.RPCURL)
   312  	if err != nil {
   313  		return err
   314  	}
   315  	b.RPC = rc
   316  	b.Client = ec
   317  	return b.subscribeEvents()
   318  }
   319  
   320  // Shutdown cleans up rpc interface to xcb
   321  func (b *CoreblockchainRPC) Shutdown(ctx context.Context) error {
   322  	b.closeRPC()
   323  	b.NewBlock.Close()
   324  	b.NewTx.Close()
   325  	glog.Info("rpc: shutdown")
   326  	return nil
   327  }
   328  
   329  // GetCoinName returns coin name
   330  func (b *CoreblockchainRPC) GetCoinName() string {
   331  	return b.ChainConfig.CoinName
   332  }
   333  
   334  // GetSubversion returns empty string, core coin does not have subversion
   335  func (b *CoreblockchainRPC) GetSubversion() string {
   336  	return ""
   337  }
   338  
   339  // GetChainInfo returns information about the connected backend
   340  func (b *CoreblockchainRPC) GetChainInfo() (*bchain.ChainInfo, error) {
   341  	h, err := b.getBestHeader()
   342  	if err != nil {
   343  		return nil, err
   344  	}
   345  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   346  	defer cancel()
   347  	id, err := b.Client.NetworkID(ctx)
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  	var ver string
   352  	if err := b.RPC.CallContext(ctx, &ver, "web3_clientVersion"); err != nil {
   353  		return nil, err
   354  	}
   355  	rv := &bchain.ChainInfo{
   356  		Blocks:        int(h.Number().Int64()),
   357  		Bestblockhash: h.Hash(),
   358  		Difficulty:    h.Difficulty().String(),
   359  		Version:       ver,
   360  	}
   361  	idi := int(id.Uint64())
   362  	if idi == int(b.MainNetChainID) {
   363  		rv.Chain = "mainnet"
   364  	} else {
   365  		rv.Chain = "testnet " + strconv.Itoa(idi)
   366  	}
   367  	return rv, nil
   368  }
   369  
   370  func (b *CoreblockchainRPC) getBestHeader() (CVMHeader, error) {
   371  	b.bestHeaderLock.Lock()
   372  	defer b.bestHeaderLock.Unlock()
   373  	// if the best header was not updated for 15 minutes, there could be a subscription problem, reconnect RPC
   374  	// do it only in case of normal operation, not initial synchronization
   375  	if b.bestHeaderTime.Add(15*time.Minute).Before(time.Now()) && !b.bestHeaderTime.IsZero() && b.mempoolInitialized {
   376  		err := b.reconnectRPC()
   377  		if err != nil {
   378  			return nil, err
   379  		}
   380  		b.bestHeader = nil
   381  	}
   382  	if b.bestHeader == nil {
   383  		var err error
   384  		ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   385  		defer cancel()
   386  		b.bestHeader, err = b.Client.HeaderByNumber(ctx, nil)
   387  		if err != nil {
   388  			b.bestHeader = nil
   389  			return nil, err
   390  		}
   391  		b.bestHeaderTime = time.Now()
   392  	}
   393  	return b.bestHeader, nil
   394  }
   395  
   396  // UpdateBestHeader keeps track of the latest block header confirmed on chain
   397  func (b *CoreblockchainRPC) UpdateBestHeader(h CVMHeader) {
   398  	glog.V(2).Info("rpc: new block header ", h.Number())
   399  	b.bestHeaderLock.Lock()
   400  	b.bestHeader = h
   401  	b.bestHeaderTime = time.Now()
   402  	b.bestHeaderLock.Unlock()
   403  }
   404  
   405  // GetBestBlockHash returns hash of the tip of the best-block-chain
   406  func (b *CoreblockchainRPC) GetBestBlockHash() (string, error) {
   407  	h, err := b.getBestHeader()
   408  	if err != nil {
   409  		return "", err
   410  	}
   411  	return h.Hash(), nil
   412  }
   413  
   414  // GetBestBlockHeight returns height of the tip of the best-block-chain
   415  func (b *CoreblockchainRPC) GetBestBlockHeight() (uint32, error) {
   416  	h, err := b.getBestHeader()
   417  	if err != nil {
   418  		return 0, err
   419  	}
   420  	return uint32(h.Number().Uint64()), nil
   421  }
   422  
   423  // GetBlockHash returns hash of block in best-block-chain at given height
   424  func (b *CoreblockchainRPC) GetBlockHash(height uint32) (string, error) {
   425  	var n big.Int
   426  	n.SetUint64(uint64(height))
   427  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   428  	defer cancel()
   429  	h, err := b.Client.HeaderByNumber(ctx, &n)
   430  	if err != nil {
   431  		if err == core.NotFound {
   432  			return "", bchain.ErrBlockNotFound
   433  		}
   434  		return "", errors.Annotatef(err, "height %v", height)
   435  	}
   436  	return h.Hash(), nil
   437  }
   438  
   439  func (b *CoreblockchainRPC) xcbHeaderToBlockHeader(h *rpcHeader) (*bchain.BlockHeader, error) {
   440  	height, err := xcbNumber(h.Number)
   441  	if err != nil {
   442  		return nil, err
   443  	}
   444  	c, err := b.computeConfirmations(uint64(height))
   445  	if err != nil {
   446  		return nil, err
   447  	}
   448  	time, err := xcbNumber(h.Time)
   449  	if err != nil {
   450  		return nil, err
   451  	}
   452  	size, err := xcbNumber(h.Size)
   453  	if err != nil {
   454  		return nil, err
   455  	}
   456  	return &bchain.BlockHeader{
   457  		Hash:          h.Hash,
   458  		Prev:          h.ParentHash,
   459  		Height:        uint32(height),
   460  		Confirmations: int(c),
   461  		Time:          time,
   462  		Size:          int(size),
   463  	}, nil
   464  }
   465  
   466  // GetBlockHeader returns header of block with given hash
   467  func (b *CoreblockchainRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
   468  	raw, err := b.getBlockRaw(hash, 0, false)
   469  	if err != nil {
   470  		return nil, err
   471  	}
   472  	var h rpcHeader
   473  	if err := json.Unmarshal(raw, &h); err != nil {
   474  		return nil, errors.Annotatef(err, "hash %v", hash)
   475  	}
   476  	return b.xcbHeaderToBlockHeader(&h)
   477  }
   478  
   479  func (b *CoreblockchainRPC) computeConfirmations(n uint64) (uint32, error) {
   480  	bh, err := b.getBestHeader()
   481  	if err != nil {
   482  		return 0, err
   483  	}
   484  	bn := bh.Number().Uint64()
   485  	// transaction in the best block has 1 confirmation
   486  	return uint32(bn - n + 1), nil
   487  }
   488  
   489  func (b *CoreblockchainRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (json.RawMessage, error) {
   490  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   491  	defer cancel()
   492  	var raw json.RawMessage
   493  	var err error
   494  	if hash != "" {
   495  		if hash == "pending" {
   496  			err = b.RPC.CallContext(ctx, &raw, "xcb_getBlockByNumber", hash, fullTxs)
   497  		} else {
   498  			err = b.RPC.CallContext(ctx, &raw, "xcb_getBlockByHash", xcbcommon.HexToHash(hash), fullTxs)
   499  		}
   500  	} else {
   501  		err = b.RPC.CallContext(ctx, &raw, "xcb_getBlockByNumber", fmt.Sprintf("%#x", height), fullTxs)
   502  	}
   503  	if err != nil {
   504  		return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
   505  	} else if len(raw) == 0 || (len(raw) == 4 && string(raw) == "null") {
   506  		return nil, bchain.ErrBlockNotFound
   507  	}
   508  	return raw, nil
   509  }
   510  
   511  func (b *CoreblockchainRPC) getTokenEventsForBlock(blockNumber string) (map[string][]*RpcLog, error) {
   512  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   513  	defer cancel()
   514  	var logs []rpcLogWithTxHash
   515  	err := b.RPC.CallContext(ctx, &logs, "xcb_getLogs", map[string]interface{}{
   516  		"fromBlock": blockNumber,
   517  		"toBlock":   blockNumber,
   518  		"topics":    []string{tokenTransferEventSignature},
   519  	})
   520  	if err != nil {
   521  		return nil, errors.Annotatef(err, "xcb_getLogs blockNumber %v", blockNumber)
   522  	}
   523  	r := make(map[string][]*RpcLog)
   524  	for i := range logs {
   525  		l := &logs[i]
   526  		r[l.Hash] = append(r[l.Hash], &l.RpcLog)
   527  	}
   528  	return r, nil
   529  }
   530  
   531  // GetBlock returns block with given hash or height, hash has precedence if both passed
   532  func (b *CoreblockchainRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
   533  	raw, err := b.getBlockRaw(hash, height, true)
   534  	if err != nil {
   535  		return nil, err
   536  	}
   537  	var head rpcHeader
   538  	if err := json.Unmarshal(raw, &head); err != nil {
   539  		return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
   540  	}
   541  	var body rpcBlockTransactions
   542  	if err := json.Unmarshal(raw, &body); err != nil {
   543  		return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
   544  	}
   545  	bbh, err := b.xcbHeaderToBlockHeader(&head)
   546  	if err != nil {
   547  		return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
   548  	}
   549  	// get token events
   550  	logs, err := b.getTokenEventsForBlock(head.Number)
   551  	if err != nil {
   552  		return nil, err
   553  	}
   554  	btxs := make([]bchain.Tx, len(body.Transactions))
   555  	for i := range body.Transactions {
   556  		tx := &body.Transactions[i]
   557  		btx, err := b.Parser.xcbTxToTx(tx, &RpcReceipt{Logs: logs[tx.Hash]}, bbh.Time, uint32(bbh.Confirmations))
   558  		if err != nil {
   559  			return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash)
   560  		}
   561  		btxs[i] = *btx
   562  		if b.mempoolInitialized {
   563  			b.Mempool.RemoveTransactionFromMempool(tx.Hash)
   564  		}
   565  	}
   566  	bbk := bchain.Block{
   567  		BlockHeader: *bbh,
   568  		Txs:         btxs,
   569  	}
   570  	return &bbk, nil
   571  }
   572  
   573  // GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids
   574  func (b *CoreblockchainRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
   575  	raw, err := b.getBlockRaw(hash, 0, false)
   576  	if err != nil {
   577  		return nil, err
   578  	}
   579  	var head rpcHeader
   580  	var txs rpcBlockTxids
   581  	if err := json.Unmarshal(raw, &head); err != nil {
   582  		return nil, errors.Annotatef(err, "hash %v", hash)
   583  	}
   584  	if err = json.Unmarshal(raw, &txs); err != nil {
   585  		return nil, err
   586  	}
   587  	bch, err := b.xcbHeaderToBlockHeader(&head)
   588  	if err != nil {
   589  		return nil, err
   590  	}
   591  	return &bchain.BlockInfo{
   592  		BlockHeader: *bch,
   593  		Difficulty:  common.JSONNumber(head.Difficulty),
   594  		Nonce:       common.JSONNumber(head.Nonce),
   595  		Txids:       txs.Transactions,
   596  	}, nil
   597  }
   598  
   599  // GetTransactionForMempool returns a transaction by the transaction ID.
   600  // It could be optimized for mempool, i.e. without block time and confirmations
   601  func (b *CoreblockchainRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
   602  	return b.GetTransaction(txid)
   603  }
   604  
   605  // GetTransaction returns a transaction by the transaction ID.
   606  func (b *CoreblockchainRPC) GetTransaction(txid string) (*bchain.Tx, error) {
   607  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   608  	defer cancel()
   609  	var tx *RpcTransaction
   610  	hash := xcbcommon.HexToHash(txid)
   611  	err := b.RPC.CallContext(ctx, &tx, "xcb_getTransactionByHash", hash)
   612  	if err != nil {
   613  		return nil, err
   614  	} else if tx == nil {
   615  		if b.mempoolInitialized {
   616  			b.Mempool.RemoveTransactionFromMempool(txid)
   617  		}
   618  		return nil, bchain.ErrTxNotFound
   619  	}
   620  	var btx *bchain.Tx
   621  	if tx.BlockNumber == "" {
   622  		// mempool tx
   623  		btx, err = b.Parser.xcbTxToTx(tx, nil, 0, 0)
   624  		if err != nil {
   625  			return nil, errors.Annotatef(err, "txid %v", txid)
   626  		}
   627  	} else {
   628  		// non mempool tx - read the block header to get the block time
   629  		raw, err := b.getBlockRaw(tx.BlockHash, 0, false)
   630  		if err != nil {
   631  			return nil, err
   632  		}
   633  		var ht struct {
   634  			Time string `json:"timestamp"`
   635  		}
   636  		if err := json.Unmarshal(raw, &ht); err != nil {
   637  			return nil, errors.Annotatef(err, "hash %v", hash)
   638  		}
   639  		var time int64
   640  		if time, err = xcbNumber(ht.Time); err != nil {
   641  			return nil, errors.Annotatef(err, "txid %v", txid)
   642  		}
   643  		var receipt RpcReceipt
   644  		err = b.RPC.CallContext(ctx, &receipt, "xcb_getTransactionReceipt", hash)
   645  		if err != nil {
   646  			return nil, errors.Annotatef(err, "txid %v", txid)
   647  		}
   648  		n, err := xcbNumber(tx.BlockNumber)
   649  		if err != nil {
   650  			return nil, errors.Annotatef(err, "txid %v", txid)
   651  		}
   652  		confirmations, err := b.computeConfirmations(uint64(n))
   653  		if err != nil {
   654  			return nil, errors.Annotatef(err, "txid %v", txid)
   655  		}
   656  		btx, err = b.Parser.xcbTxToTx(tx, &receipt, time, confirmations)
   657  		if err != nil {
   658  			return nil, errors.Annotatef(err, "txid %v", txid)
   659  		}
   660  		// remove tx from mempool if it is there
   661  		if b.mempoolInitialized {
   662  			b.Mempool.RemoveTransactionFromMempool(txid)
   663  		}
   664  	}
   665  	return btx, nil
   666  }
   667  
   668  // GetTransactionSpecific returns json as returned by backend, with all coin specific data
   669  func (b *CoreblockchainRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
   670  	csd, ok := tx.CoinSpecificData.(CoreCoinSpecificData)
   671  	if !ok {
   672  		ntx, err := b.GetTransaction(tx.Txid)
   673  		if err != nil {
   674  			return nil, err
   675  		}
   676  		csd, ok = ntx.CoinSpecificData.(CoreCoinSpecificData)
   677  		if !ok {
   678  			return nil, errors.New("Cannot get CoinSpecificData")
   679  		}
   680  	}
   681  	m, err := json.Marshal(&csd)
   682  	return json.RawMessage(m), err
   683  }
   684  
   685  // GetMempoolTransactions returns transactions in mempool
   686  func (b *CoreblockchainRPC) GetMempoolTransactions() ([]string, error) {
   687  	raw, err := b.getBlockRaw("pending", 0, false)
   688  	if err != nil {
   689  		return nil, err
   690  	}
   691  	var body rpcBlockTxids
   692  	if len(raw) > 0 {
   693  		if err := json.Unmarshal(raw, &body); err != nil {
   694  			return nil, err
   695  		}
   696  	}
   697  	return body.Transactions, nil
   698  }
   699  
   700  // EstimateFee returns fee estimation
   701  func (b *CoreblockchainRPC) EstimateFee(blocks int) (big.Int, error) {
   702  	return b.EstimateSmartFee(blocks, true)
   703  }
   704  
   705  // EstimateSmartFee returns fee estimation
   706  func (b *CoreblockchainRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) {
   707  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   708  	defer cancel()
   709  	var r big.Int
   710  	gp, err := b.Client.SuggestEnergyPrice(ctx)
   711  	if err == nil && b != nil {
   712  		r = *gp
   713  	}
   714  	return r, err
   715  }
   716  
   717  // GetStringFromMap attempts to return the value for a specific key in a map as a string if valid,
   718  // otherwise returns an empty string with false indicating there was no key found, or the value was not a string
   719  func GetStringFromMap(p string, params map[string]interface{}) (string, bool) {
   720  	v, ok := params[p]
   721  	if ok {
   722  		s, ok := v.(string)
   723  		return s, ok
   724  	}
   725  	return "", false
   726  }
   727  
   728  // CoreCoinTypeEstimateEnergy returns estimation of energy consumption for given transaction parameters
   729  func (b *CoreblockchainRPC) CoreCoinTypeEstimateEnergy(params map[string]interface{}) (uint64, error) {
   730  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   731  	defer cancel()
   732  	msg := core.CallMsg{}
   733  	if s, ok := GetStringFromMap("from", params); ok && len(s) > 0 {
   734  		addr, err := xcbcommon.HexToAddress(s)
   735  		if err != nil {
   736  			return 0, err
   737  		}
   738  		msg.From = addr
   739  	}
   740  	if s, ok := GetStringFromMap("to", params); ok && len(s) > 0 {
   741  		a, err := xcbcommon.HexToAddress(s)
   742  		if err != nil {
   743  			return 0, err
   744  		}
   745  		msg.To = &a
   746  	}
   747  	if s, ok := GetStringFromMap("data", params); ok && len(s) > 0 {
   748  		msg.Data = xcbcommon.FromHex(s)
   749  	}
   750  	if s, ok := GetStringFromMap("value", params); ok && len(s) > 0 {
   751  		msg.Value, _ = hexutil.DecodeBig(s)
   752  	}
   753  	if s, ok := GetStringFromMap("energy", params); ok && len(s) > 0 {
   754  		msg.Energy, _ = hexutil.DecodeUint64(s)
   755  	}
   756  	if s, ok := GetStringFromMap("energyPrice", params); ok && len(s) > 0 {
   757  		msg.EnergyPrice, _ = hexutil.DecodeBig(s)
   758  	}
   759  	return b.Client.EstimateEnergy(ctx, msg)
   760  }
   761  
   762  // SendRawTransaction sends raw transaction
   763  func (b *CoreblockchainRPC) SendRawTransaction(hex string) (string, error) {
   764  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   765  	defer cancel()
   766  	var raw json.RawMessage
   767  	err := b.RPC.CallContext(ctx, &raw, "xcb_sendRawTransaction", hex)
   768  	if err != nil {
   769  		return "", err
   770  	} else if len(raw) == 0 {
   771  		return "", errors.New("SendRawTransaction: failed")
   772  	}
   773  	var result string
   774  	if err := json.Unmarshal(raw, &result); err != nil {
   775  		return "", errors.Annotatef(err, "raw result %v", raw)
   776  	}
   777  	if result == "" {
   778  		return "", errors.New("SendRawTransaction: failed, empty result")
   779  	}
   780  	return result, nil
   781  }
   782  
   783  // CoreCoinTypeGetBalance returns current balance of an address
   784  func (b *CoreblockchainRPC) CoreCoinTypeGetBalance(addrDesc bchain.AddressDescriptor) (*big.Int, error) {
   785  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   786  	defer cancel()
   787  	return b.Client.BalanceAt(ctx, addrDesc, nil)
   788  }
   789  
   790  // CoreCoinTypeGetNonce returns current balance of an address
   791  func (b *CoreblockchainRPC) CoreCoinTypeGetNonce(addrDesc bchain.AddressDescriptor) (uint64, error) {
   792  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   793  	defer cancel()
   794  	return b.Client.NonceAt(ctx, addrDesc, nil)
   795  }
   796  
   797  // GetChainParser returns core coin BlockChainParser
   798  func (b *CoreblockchainRPC) GetChainParser() bchain.BlockChainParser {
   799  	return b.Parser
   800  }