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 }