github.com/evdatsion/aphelion-dpos-bft@v0.32.1/rpc/core/mempool.go (about)

     1  package core
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/pkg/errors"
     9  
    10  	abci "github.com/evdatsion/aphelion-dpos-bft/abci/types"
    11  	ctypes "github.com/evdatsion/aphelion-dpos-bft/rpc/core/types"
    12  	rpctypes "github.com/evdatsion/aphelion-dpos-bft/rpc/lib/types"
    13  	"github.com/evdatsion/aphelion-dpos-bft/types"
    14  )
    15  
    16  //-----------------------------------------------------------------------------
    17  // NOTE: tx should be signed, but this is only checked at the app level (not by Tendermint!)
    18  
    19  // Returns right away, with no response. Does not wait for CheckTx nor
    20  // DeliverTx results.
    21  //
    22  // If you want to be sure that the transaction is included in a block, you can
    23  // subscribe for the result using JSONRPC via a websocket. See
    24  // https:/.com/docs/app-dev/subscribing-to-events-via-websocket.html
    25  // If you haven't received anything after a couple of blocks, resend it. If the
    26  // same happens again, send it to some other node. A few reasons why it could
    27  // happen:
    28  //
    29  // 1. malicious node can drop or pretend it had committed your tx
    30  // 2. malicious proposer (not necessary the one you're communicating with) can
    31  // drop transactions, which might become valid in the future
    32  // (https://github.com/evdatsion/aphelion-dpos-bft/issues/3322)
    33  // 3. node can be offline
    34  //
    35  // Please refer to
    36  // https:/.com/docs-core/using-tendermint.html#formatting
    37  // for formatting/encoding rules.
    38  //
    39  //
    40  // ```shell
    41  // curl 'localhost:26657/broadcast_tx_async?tx="123"'
    42  // ```
    43  //
    44  // ```go
    45  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
    46  // err := client.Start()
    47  // if err != nil {
    48  //   // handle error
    49  // }
    50  // defer client.Stop()
    51  // result, err := client.BroadcastTxAsync("123")
    52  // ```
    53  //
    54  // > The above command returns JSON structured like this:
    55  //
    56  // ```json
    57  // {
    58  // 	"error": "",
    59  // 	"result": {
    60  // 		"hash": "E39AAB7A537ABAA237831742DCE1117F187C3C52",
    61  // 		"log": "",
    62  // 		"data": "",
    63  // 		"code": "0"
    64  // 	},
    65  // 	"id": "",
    66  // 	"jsonrpc": "2.0"
    67  // }
    68  // ```
    69  //
    70  // ### Query Parameters
    71  //
    72  // | Parameter | Type | Default | Required | Description     |
    73  // |-----------+------+---------+----------+-----------------|
    74  // | tx        | Tx   | nil     | true     | The transaction |
    75  func BroadcastTxAsync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
    76  	err := mempool.CheckTx(tx, nil)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	return &ctypes.ResultBroadcastTx{Hash: tx.Hash()}, nil
    81  }
    82  
    83  // Returns with the response from CheckTx. Does not wait for DeliverTx result.
    84  //
    85  // If you want to be sure that the transaction is included in a block, you can
    86  // subscribe for the result using JSONRPC via a websocket. See
    87  // https:/.com/docs/app-dev/subscribing-to-events-via-websocket.html
    88  // If you haven't received anything after a couple of blocks, resend it. If the
    89  // same happens again, send it to some other node. A few reasons why it could
    90  // happen:
    91  //
    92  // 1. malicious node can drop or pretend it had committed your tx
    93  // 2. malicious proposer (not necessary the one you're communicating with) can
    94  // drop transactions, which might become valid in the future
    95  // (https://github.com/evdatsion/aphelion-dpos-bft/issues/3322)
    96  //
    97  // Please refer to
    98  // https:/.com/docs-core/using-tendermint.html#formatting
    99  // for formatting/encoding rules.
   100  //
   101  // ```shell
   102  // curl 'localhost:26657/broadcast_tx_sync?tx="456"'
   103  // ```
   104  //
   105  // ```go
   106  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   107  // err := client.Start()
   108  // if err != nil {
   109  //   // handle error
   110  // }
   111  // defer client.Stop()
   112  // result, err := client.BroadcastTxSync("456")
   113  // ```
   114  //
   115  // > The above command returns JSON structured like this:
   116  //
   117  // ```json
   118  // {
   119  // 	"jsonrpc": "2.0",
   120  // 	"id": "",
   121  // 	"result": {
   122  // 		"code": "0",
   123  // 		"data": "",
   124  // 		"log": "",
   125  // 		"hash": "0D33F2F03A5234F38706E43004489E061AC40A2E"
   126  // 	},
   127  // 	"error": ""
   128  // }
   129  // ```
   130  //
   131  // ### Query Parameters
   132  //
   133  // | Parameter | Type | Default | Required | Description     |
   134  // |-----------+------+---------+----------+-----------------|
   135  // | tx        | Tx   | nil     | true     | The transaction |
   136  func BroadcastTxSync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
   137  	resCh := make(chan *abci.Response, 1)
   138  	err := mempool.CheckTx(tx, func(res *abci.Response) {
   139  		resCh <- res
   140  	})
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	res := <-resCh
   145  	r := res.GetCheckTx()
   146  	return &ctypes.ResultBroadcastTx{
   147  		Code: r.Code,
   148  		Data: r.Data,
   149  		Log:  r.Log,
   150  		Hash: tx.Hash(),
   151  	}, nil
   152  }
   153  
   154  // Returns with the responses from CheckTx and DeliverTx.
   155  //
   156  // IMPORTANT: use only for testing and development. In production, use
   157  // BroadcastTxSync or BroadcastTxAsync. You can subscribe for the transaction
   158  // result using JSONRPC via a websocket. See
   159  // https:/.com/docs/app-dev/subscribing-to-events-via-websocket.html
   160  //
   161  // CONTRACT: only returns error if mempool.CheckTx() errs or if we timeout
   162  // waiting for tx to commit.
   163  //
   164  // If CheckTx or DeliverTx fail, no error will be returned, but the returned result
   165  // will contain a non-OK ABCI code.
   166  //
   167  // Please refer to
   168  // https:/.com/docs-core/using-tendermint.html#formatting
   169  // for formatting/encoding rules.
   170  //
   171  //
   172  // ```shell
   173  // curl 'localhost:26657/broadcast_tx_commit?tx="789"'
   174  // ```
   175  //
   176  // ```go
   177  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   178  // err := client.Start()
   179  // if err != nil {
   180  //   // handle error
   181  // }
   182  // defer client.Stop()
   183  // result, err := client.BroadcastTxCommit("789")
   184  // ```
   185  //
   186  // > The above command returns JSON structured like this:
   187  //
   188  // ```json
   189  // {
   190  // 	"error": "",
   191  // 	"result": {
   192  // 		"height": "26682",
   193  // 		"hash": "75CA0F856A4DA078FC4911580360E70CEFB2EBEE",
   194  // 		"deliver_tx": {
   195  // 			"log": "",
   196  // 			"data": "",
   197  // 			"code": "0"
   198  // 		},
   199  // 		"check_tx": {
   200  // 			"log": "",
   201  // 			"data": "",
   202  // 			"code": "0"
   203  // 		}
   204  // 	},
   205  // 	"id": "",
   206  // 	"jsonrpc": "2.0"
   207  // }
   208  // ```
   209  //
   210  // ### Query Parameters
   211  //
   212  // | Parameter | Type | Default | Required | Description     |
   213  // |-----------+------+---------+----------+-----------------|
   214  // | tx        | Tx   | nil     | true     | The transaction |
   215  func BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
   216  	subscriber := ctx.RemoteAddr()
   217  
   218  	if eventBus.NumClients() >= config.MaxSubscriptionClients {
   219  		return nil, fmt.Errorf("max_subscription_clients %d reached", config.MaxSubscriptionClients)
   220  	} else if eventBus.NumClientSubscriptions(subscriber) >= config.MaxSubscriptionsPerClient {
   221  		return nil, fmt.Errorf("max_subscriptions_per_client %d reached", config.MaxSubscriptionsPerClient)
   222  	}
   223  
   224  	// Subscribe to tx being committed in block.
   225  	subCtx, cancel := context.WithTimeout(ctx.Context(), SubscribeTimeout)
   226  	defer cancel()
   227  	q := types.EventQueryTxFor(tx)
   228  	deliverTxSub, err := eventBus.Subscribe(subCtx, subscriber, q)
   229  	if err != nil {
   230  		err = errors.Wrap(err, "failed to subscribe to tx")
   231  		logger.Error("Error on broadcast_tx_commit", "err", err)
   232  		return nil, err
   233  	}
   234  	defer eventBus.Unsubscribe(context.Background(), subscriber, q)
   235  
   236  	// Broadcast tx and wait for CheckTx result
   237  	checkTxResCh := make(chan *abci.Response, 1)
   238  	err = mempool.CheckTx(tx, func(res *abci.Response) {
   239  		checkTxResCh <- res
   240  	})
   241  	if err != nil {
   242  		logger.Error("Error on broadcastTxCommit", "err", err)
   243  		return nil, fmt.Errorf("Error on broadcastTxCommit: %v", err)
   244  	}
   245  	checkTxResMsg := <-checkTxResCh
   246  	checkTxRes := checkTxResMsg.GetCheckTx()
   247  	if checkTxRes.Code != abci.CodeTypeOK {
   248  		return &ctypes.ResultBroadcastTxCommit{
   249  			CheckTx:   *checkTxRes,
   250  			DeliverTx: abci.ResponseDeliverTx{},
   251  			Hash:      tx.Hash(),
   252  		}, nil
   253  	}
   254  
   255  	// Wait for the tx to be included in a block or timeout.
   256  	select {
   257  	case msg := <-deliverTxSub.Out(): // The tx was included in a block.
   258  		deliverTxRes := msg.Data().(types.EventDataTx)
   259  		return &ctypes.ResultBroadcastTxCommit{
   260  			CheckTx:   *checkTxRes,
   261  			DeliverTx: deliverTxRes.Result,
   262  			Hash:      tx.Hash(),
   263  			Height:    deliverTxRes.Height,
   264  		}, nil
   265  	case <-deliverTxSub.Cancelled():
   266  		var reason string
   267  		if deliverTxSub.Err() == nil {
   268  			reason = "Tendermint exited"
   269  		} else {
   270  			reason = deliverTxSub.Err().Error()
   271  		}
   272  		err = fmt.Errorf("deliverTxSub was cancelled (reason: %s)", reason)
   273  		logger.Error("Error on broadcastTxCommit", "err", err)
   274  		return &ctypes.ResultBroadcastTxCommit{
   275  			CheckTx:   *checkTxRes,
   276  			DeliverTx: abci.ResponseDeliverTx{},
   277  			Hash:      tx.Hash(),
   278  		}, err
   279  	case <-time.After(config.TimeoutBroadcastTxCommit):
   280  		err = errors.New("Timed out waiting for tx to be included in a block")
   281  		logger.Error("Error on broadcastTxCommit", "err", err)
   282  		return &ctypes.ResultBroadcastTxCommit{
   283  			CheckTx:   *checkTxRes,
   284  			DeliverTx: abci.ResponseDeliverTx{},
   285  			Hash:      tx.Hash(),
   286  		}, err
   287  	}
   288  }
   289  
   290  // Get unconfirmed transactions (maximum ?limit entries) including their number.
   291  //
   292  // ```shell
   293  // curl 'localhost:26657/unconfirmed_txs'
   294  // ```
   295  //
   296  // ```go
   297  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   298  // err := client.Start()
   299  // if err != nil {
   300  //   // handle error
   301  // }
   302  // defer client.Stop()
   303  // result, err := client.UnconfirmedTxs()
   304  // ```
   305  //
   306  // > The above command returns JSON structured like this:
   307  //
   308  // ```json
   309  // {
   310  //   "result" : {
   311  //       "txs" : [],
   312  //       "total_bytes" : "0",
   313  //       "n_txs" : "0",
   314  //       "total" : "0"
   315  //     },
   316  //     "jsonrpc" : "2.0",
   317  //     "id" : ""
   318  //   }
   319  // ```
   320  //
   321  // ### Query Parameters
   322  //
   323  // | Parameter | Type | Default | Required | Description                          |
   324  // |-----------+------+---------+----------+--------------------------------------|
   325  // | limit     | int  | 30      | false    | Maximum number of entries (max: 100) |
   326  // ```
   327  func UnconfirmedTxs(ctx *rpctypes.Context, limit int) (*ctypes.ResultUnconfirmedTxs, error) {
   328  	// reuse per_page validator
   329  	limit = validatePerPage(limit)
   330  
   331  	txs := mempool.ReapMaxTxs(limit)
   332  	return &ctypes.ResultUnconfirmedTxs{
   333  		Count:      len(txs),
   334  		Total:      mempool.Size(),
   335  		TotalBytes: mempool.TxsBytes(),
   336  		Txs:        txs}, nil
   337  }
   338  
   339  // Get number of unconfirmed transactions.
   340  //
   341  // ```shell
   342  // curl 'localhost:26657/num_unconfirmed_txs'
   343  // ```
   344  //
   345  // ```go
   346  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   347  // err := client.Start()
   348  // if err != nil {
   349  //   // handle error
   350  // }
   351  // defer client.Stop()
   352  // result, err := client.UnconfirmedTxs()
   353  // ```
   354  //
   355  // > The above command returns JSON structured like this:
   356  //
   357  // ```json
   358  // {
   359  //   "jsonrpc" : "2.0",
   360  //   "id" : "",
   361  //   "result" : {
   362  //     "n_txs" : "0",
   363  //     "total_bytes" : "0",
   364  //     "txs" : null,
   365  //     "total" : "0"
   366  //   }
   367  // }
   368  // ```
   369  func NumUnconfirmedTxs(ctx *rpctypes.Context) (*ctypes.ResultUnconfirmedTxs, error) {
   370  	return &ctypes.ResultUnconfirmedTxs{
   371  		Count:      mempool.Size(),
   372  		Total:      mempool.Size(),
   373  		TotalBytes: mempool.TxsBytes()}, nil
   374  }