github.com/dominant-strategies/go-quai@v0.28.2/quaiclient/quaiclient.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package ethclient provides a client for the Quai RPC API.
    18  package quaiclient
    19  
    20  import (
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"math/big"
    25  	"time"
    26  
    27  	quai "github.com/dominant-strategies/go-quai"
    28  	"github.com/dominant-strategies/go-quai/common"
    29  	"github.com/dominant-strategies/go-quai/common/hexutil"
    30  	"github.com/dominant-strategies/go-quai/core/types"
    31  	"github.com/dominant-strategies/go-quai/log"
    32  	"github.com/dominant-strategies/go-quai/rpc"
    33  )
    34  
    35  var exponentialBackoffCeilingSecs int64 = 60 // 1 minute
    36  
    37  // Client defines typed wrappers for the Quai RPC API.
    38  type Client struct {
    39  	c *rpc.Client
    40  }
    41  
    42  // Dial connects a client to the given URL.
    43  func Dial(rawurl string) (*Client, error) {
    44  	return DialContext(context.Background(), rawurl)
    45  }
    46  
    47  func DialContext(ctx context.Context, rawurl string) (*Client, error) {
    48  	connectStatus := false
    49  	attempts := 0
    50  
    51  	var c *rpc.Client
    52  	var err error
    53  	for !connectStatus {
    54  		c, err = rpc.DialContext(ctx, rawurl)
    55  		if err == nil {
    56  			break
    57  		}
    58  
    59  		attempts += 1
    60  		// exponential back-off implemented
    61  		// delaySecs := int64(math.Floor((math.Pow(2, float64(attempts)) - 1) * 0.5))
    62  		delaySecs := int64(1)
    63  		if delaySecs > exponentialBackoffCeilingSecs {
    64  			return nil, err
    65  		}
    66  
    67  		// should only get here if the ffmpeg record stream process dies
    68  		log.Warn("Attempting to connect to go-quai node. Waiting and retrying...", "attempts", attempts, "delay", delaySecs, "url", rawurl)
    69  
    70  		time.Sleep(time.Duration(delaySecs) * time.Second)
    71  	}
    72  
    73  	return NewClient(c), nil
    74  }
    75  
    76  // NewClient creates a client that uses the given RPC client.
    77  func NewClient(c *rpc.Client) *Client {
    78  	return &Client{c}
    79  }
    80  
    81  func (ec *Client) Close() {
    82  	ec.c.Close()
    83  }
    84  
    85  type Termini struct {
    86  	Termini []common.Hash `json:"termini"`
    87  }
    88  
    89  type appendReturns struct {
    90  	Etxs     types.Transactions `json:"pendingEtxs"`
    91  	SubReorg bool               `json:"subReorg"`
    92  	SetHead  bool               `json:"setHead"`
    93  }
    94  
    95  // SubscribePendingHeader subscribes to notifications about the current pending block on the node.
    96  func (ec *Client) SubscribePendingHeader(ctx context.Context, ch chan<- *types.Header) (quai.Subscription, error) {
    97  	return ec.c.QuaiSubscribe(ctx, ch, "pendingHeader")
    98  }
    99  
   100  func (ec *Client) Append(ctx context.Context, header *types.Header, manifest types.BlockManifest, domPendingHeader *types.Header, domTerminus common.Hash, domOrigin bool, newInboundEtxs types.Transactions) (types.Transactions, bool, bool, error) {
   101  	fields := map[string]interface{}{
   102  		"header":           header.RPCMarshalHeader(),
   103  		"manifest":         manifest,
   104  		"domPendingHeader": domPendingHeader.RPCMarshalHeader(),
   105  		"domTerminus":      domTerminus,
   106  		"domOrigin":        domOrigin,
   107  		"newInboundEtxs":   newInboundEtxs,
   108  	}
   109  
   110  	var raw json.RawMessage
   111  	err := ec.c.CallContext(ctx, &raw, "quai_append", fields)
   112  	if err != nil {
   113  		return nil, false, false, err
   114  	}
   115  
   116  	// Decode header and transactions.
   117  	var aReturns appendReturns
   118  	if err := json.Unmarshal(raw, &aReturns); err != nil {
   119  		return nil, false, false, err
   120  	}
   121  
   122  	return aReturns.Etxs, aReturns.SubReorg, aReturns.SetHead, nil
   123  }
   124  
   125  func (ec *Client) DownloadBlocksInManifest(ctx context.Context, hash common.Hash, manifest types.BlockManifest, entropy *big.Int) {
   126  	fields := map[string]interface{}{
   127  		"hash":     hash,
   128  		"manifest": manifest,
   129  		"entropy":  entropy,
   130  	}
   131  	ec.c.CallContext(ctx, nil, "quai_downloadBlocksInManifest", fields)
   132  }
   133  
   134  func (ec *Client) SubRelayPendingHeader(ctx context.Context, pendingHeader types.PendingHeader, newEntropy *big.Int, location common.Location, subReorg bool, order int) {
   135  	data := map[string]interface{}{"header": pendingHeader.Header().RPCMarshalHeader()}
   136  	data["NewEntropy"] = newEntropy
   137  	data["termini"] = pendingHeader.Termini().RPCMarshalTermini()
   138  	data["Location"] = location
   139  	data["SubReorg"] = subReorg
   140  	data["Order"] = order
   141  
   142  	ec.c.CallContext(ctx, nil, "quai_subRelayPendingHeader", data)
   143  }
   144  
   145  func (ec *Client) UpdateDom(ctx context.Context, oldTerminus common.Hash, pendingHeader types.PendingHeader, location common.Location) {
   146  	data := map[string]interface{}{"header": pendingHeader.Header().RPCMarshalHeader()}
   147  	data["OldTerminus"] = oldTerminus
   148  	data["Location"] = location
   149  	data["termini"] = pendingHeader.Termini().RPCMarshalTermini()
   150  
   151  	ec.c.CallContext(ctx, nil, "quai_updateDom", data)
   152  }
   153  
   154  func (ec *Client) RequestDomToAppendOrFetch(ctx context.Context, hash common.Hash, entropy *big.Int, order int) {
   155  	data := map[string]interface{}{"Hash": hash}
   156  	data["Entropy"] = entropy
   157  	data["Order"] = order
   158  
   159  	ec.c.CallContext(ctx, nil, "quai_requestDomToAppendOrFetch", data)
   160  }
   161  
   162  func (ec *Client) NewGenesisPendingHeader(ctx context.Context, header *types.Header) {
   163  	ec.c.CallContext(ctx, nil, "quai_newGenesisPendingHeader", header.RPCMarshalHeader())
   164  }
   165  
   166  // GetManifest will get the block manifest ending with the parent hash
   167  func (ec *Client) GetManifest(ctx context.Context, blockHash common.Hash) (types.BlockManifest, error) {
   168  	var raw json.RawMessage
   169  	err := ec.c.CallContext(ctx, &raw, "quai_getManifest", blockHash)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	var manifest types.BlockManifest
   174  	if err := json.Unmarshal(raw, &manifest); err != nil {
   175  		return nil, err
   176  	}
   177  	return manifest, nil
   178  }
   179  
   180  // GetPendingEtxsRollupFromSub gets the pendingEtxsRollup from the region
   181  func (ec *Client) GetPendingEtxsRollupFromSub(ctx context.Context, hash common.Hash, location common.Location) (types.PendingEtxsRollup, error) {
   182  	fields := make(map[string]interface{})
   183  	fields["Hash"] = hash
   184  	fields["Location"] = location
   185  
   186  	var raw json.RawMessage
   187  	err := ec.c.CallContext(ctx, &raw, "quai_getPendingEtxsRollupFromSub", fields)
   188  	if err != nil {
   189  		return types.PendingEtxsRollup{}, err
   190  	}
   191  
   192  	var pEtxsRollup types.PendingEtxsRollup
   193  	if err := json.Unmarshal(raw, &pEtxsRollup); err != nil {
   194  		return types.PendingEtxsRollup{}, err
   195  	}
   196  	return pEtxsRollup, nil
   197  }
   198  
   199  // GetPendingEtxsFromSub gets the pendingEtxsRollup from the region
   200  func (ec *Client) GetPendingEtxsFromSub(ctx context.Context, hash common.Hash, location common.Location) (types.PendingEtxs, error) {
   201  	fields := make(map[string]interface{})
   202  	fields["Hash"] = hash
   203  	fields["Location"] = location
   204  
   205  	var raw json.RawMessage
   206  	err := ec.c.CallContext(ctx, &raw, "quai_getPendingEtxsFromSub", fields)
   207  	if err != nil {
   208  		return types.PendingEtxs{}, err
   209  	}
   210  
   211  	var pEtxs types.PendingEtxs
   212  	if err := json.Unmarshal(raw, &pEtxs); err != nil {
   213  		return types.PendingEtxs{}, err
   214  	}
   215  	return pEtxs, nil
   216  }
   217  
   218  func (ec *Client) SendPendingEtxsToDom(ctx context.Context, pEtxs types.PendingEtxs) error {
   219  	fields := make(map[string]interface{})
   220  	fields["header"] = pEtxs.Header.RPCMarshalHeader()
   221  	fields["etxs"] = pEtxs.Etxs
   222  	var raw json.RawMessage
   223  	err := ec.c.CallContext(ctx, &raw, "quai_sendPendingEtxsToDom", fields)
   224  	if err != nil {
   225  		return err
   226  	}
   227  	return nil
   228  }
   229  
   230  func (ec *Client) SendPendingEtxsRollupToDom(ctx context.Context, pEtxsRollup types.PendingEtxsRollup) error {
   231  	fields := make(map[string]interface{})
   232  	fields["header"] = pEtxsRollup.Header.RPCMarshalHeader()
   233  	fields["manifest"] = pEtxsRollup.Manifest
   234  	var raw json.RawMessage
   235  	return ec.c.CallContext(ctx, &raw, "quai_sendPendingEtxsRollupToDom", fields)
   236  }
   237  
   238  func (ec *Client) GenerateRecoveryPendingHeader(ctx context.Context, pendingHeader *types.Header, checkpointHashes types.Termini) error {
   239  	fields := make(map[string]interface{})
   240  	fields["pendingHeader"] = pendingHeader.RPCMarshalHeader()
   241  	fields["checkpointHashes"] = checkpointHashes.RPCMarshalTermini()
   242  	return ec.c.CallContext(ctx, nil, "quai_generateRecoveryPendingHeader", fields)
   243  }
   244  
   245  func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) *types.Header {
   246  	var raw json.RawMessage
   247  	ec.c.CallContext(ctx, &raw, "quai_getHeaderByHash", hash)
   248  	var header *types.Header
   249  	if err := json.Unmarshal(raw, &header); err != nil {
   250  		return nil
   251  	}
   252  	return header
   253  }
   254  
   255  func (ec *Client) HeaderByNumber(ctx context.Context, number string) *types.Header {
   256  	var raw json.RawMessage
   257  	ec.c.CallContext(ctx, &raw, "quai_getHeaderByNumber", number)
   258  	var header *types.Header
   259  	if err := json.Unmarshal(raw, &header); err != nil {
   260  		return nil
   261  	}
   262  	return header
   263  }
   264  
   265  func (ec *Client) SetSyncTarget(ctx context.Context, header *types.Header) {
   266  	fields := header.RPCMarshalHeader()
   267  	ec.c.CallContext(ctx, nil, "quai_setSyncTarget", fields)
   268  }
   269  
   270  //// Miner APIS
   271  
   272  // GetPendingHeader gets the latest pending header from the chain.
   273  func (ec *Client) GetPendingHeader(ctx context.Context) (*types.Header, error) {
   274  	var pendingHeader *types.Header
   275  	err := ec.c.CallContext(ctx, &pendingHeader, "quai_getPendingHeader")
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  	return pendingHeader, nil
   280  }
   281  
   282  // ReceiveMinedHeader sends a mined block back to the node
   283  func (ec *Client) ReceiveMinedHeader(ctx context.Context, header *types.Header) error {
   284  	data := header.RPCMarshalHeader()
   285  	return ec.c.CallContext(ctx, nil, "quai_receiveMinedHeader", data)
   286  }
   287  
   288  // Filters
   289  
   290  // SubscribeFilterLogs subscribes to the results of a streaming filter query.
   291  func (ec *Client) SubscribeFilterLogs(ctx context.Context, q quai.FilterQuery, ch chan<- types.Log) (quai.Subscription, error) {
   292  	arg, err := toFilterArg(q)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  	return ec.c.QuaiSubscribe(ctx, ch, "logs", arg)
   297  }
   298  
   299  func toFilterArg(q quai.FilterQuery) (interface{}, error) {
   300  	arg := map[string]interface{}{
   301  		"address": q.Addresses,
   302  		"topics":  q.Topics,
   303  	}
   304  	if q.BlockHash != nil {
   305  		arg["blockHash"] = *q.BlockHash
   306  		if q.FromBlock != nil || q.ToBlock != nil {
   307  			return nil, fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock")
   308  		}
   309  	} else {
   310  		if q.FromBlock == nil {
   311  			arg["fromBlock"] = "0x0"
   312  		} else {
   313  			arg["fromBlock"] = toBlockNumArg(q.FromBlock)
   314  		}
   315  		arg["toBlock"] = toBlockNumArg(q.ToBlock)
   316  	}
   317  	return arg, nil
   318  }
   319  
   320  func toBlockNumArg(number *big.Int) string {
   321  	if number == nil {
   322  		return "latest"
   323  	}
   324  	pending := big.NewInt(-1)
   325  	if number.Cmp(pending) == 0 {
   326  		return "pending"
   327  	}
   328  	return hexutil.EncodeBig(number)
   329  }
   330  
   331  func toCallArg(msg quai.CallMsg) interface{} {
   332  	arg := map[string]interface{}{
   333  		"from": msg.From,
   334  		"to":   msg.To,
   335  	}
   336  	if len(msg.Data) > 0 {
   337  		arg["data"] = hexutil.Bytes(msg.Data)
   338  	}
   339  	if msg.Value != nil {
   340  		arg["value"] = (*hexutil.Big)(msg.Value)
   341  	}
   342  	if msg.Gas != 0 {
   343  		arg["gas"] = hexutil.Uint64(msg.Gas)
   344  	}
   345  	if msg.GasPrice != nil {
   346  		arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
   347  	}
   348  	return arg
   349  }
   350  
   351  // FilterLogs executes a filter query.
   352  func (ec *Client) FilterLogs(ctx context.Context, q quai.FilterQuery) ([]types.Log, error) {
   353  	var result []types.Log
   354  	arg, err := toFilterArg(q)
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  	err = ec.c.CallContext(ctx, &result, "quai_getLogs", arg)
   359  	return result, err
   360  }