Skip to content

Commit

Permalink
rest: Add "/broadcast" endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
danielabrozzoni committed Oct 10, 2024
1 parent caf44e5 commit 84d99da
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 3 deletions.
6 changes: 6 additions & 0 deletions doc/REST-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ Responds with 404 if the transaction doesn't exist.
By default, this endpoint will only search the mempool.
To query for a confirmed transaction, enable the transaction index via "txindex=1" command line / configuration option.

`POST /rest/broadcast.hex`

Broadcasts a transaction.
The transaction hex must be passed in the body of the request.
Returns the txid if the transaction was broadcasted correctly, responds with 400 if the transaction hex can't be parsed or if broadcasting failed.

#### Blocks
- `GET /rest/block/<BLOCK-HASH>.<bin|hex|json>`
- `GET /rest/block/notxdetails/<BLOCK-HASH>.<bin|hex|json>`
Expand Down
6 changes: 3 additions & 3 deletions src/node/transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ static TransactionError HandleATMPError(const TxValidationState& state, std::str

TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback)
{
// BroadcastTransaction can be called by RPC or by the wallet.
// chainman, mempool and peerman are initialized before the RPC server and wallet are started
// and reset after the RPC sever and wallet are stopped.
// BroadcastTransaction can be called by RPC, REST, or by the wallet.
// chainman, mempool and peerman are initialized before the RPC/REST servers and wallet are started
// and reset after the RPC/REST servers and wallet are stopped.
assert(node.chainman);
assert(node.mempool);
assert(node.peerman);
Expand Down
39 changes: 39 additions & 0 deletions src/rest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <index/txindex.h>
#include <node/blockstorage.h>
#include <node/context.h>
#include <node/types.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <rpc/blockchain.h>
Expand Down Expand Up @@ -1006,6 +1007,43 @@ static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req,
}
}

static bool rest_broadcast(const std::any& context, HTTPRequest* req, const std::string& str_uri_part) {
if(!CheckWarmup(req))
return false;
const std::string body = req->ReadBody();
std::string params;
const RESTResponseFormat rf = ParseDataFormat(params, str_uri_part);
if (params != "") {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/broadcast.hex");
}

CMutableTransaction mtx;
if (!DecodeHexTx(mtx, body)) {
return RESTERR(req, HTTP_BAD_REQUEST, "TX decode failed");
}

switch (rf) {
case RESTResponseFormat::HEX: {
const CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
std::string err_string;
NodeContext* node = GetNodeContext(context, req);
if(!node) return false;

const node::TransactionError error = node::BroadcastTransaction(*node, tx, err_string, /*max_tx_fee=*/0, /*relay=*/true, /*wait_callback=*/true);
if(node::TransactionError::OK != error) {
return RESTERR(req, HTTP_BAD_REQUEST, "Error while broadcasting: " + err_string);
}

req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, tx->GetHash().GetHex() + "\n");
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: hex)");
}
}
}

static const struct {
const char* prefix;
bool (*handler)(const std::any& context, HTTPRequest* req, const std::string& strReq);
Expand All @@ -1022,6 +1060,7 @@ static const struct {
{"/rest/deploymentinfo/", rest_deploymentinfo},
{"/rest/deploymentinfo", rest_deploymentinfo},
{"/rest/blockhashbyheight/", rest_blockhash_by_height},
{"/rest/broadcast", rest_broadcast},
};

void StartREST(const std::any& context)
Expand Down
15 changes: 15 additions & 0 deletions test/functional/interface_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,5 +440,20 @@ def run_test(self):
resp = self.test_rest_request(f"/deploymentinfo/{INVALID_PARAM}", ret_type=RetType.OBJ, status=400)
assert_equal(resp.read().decode('utf-8').rstrip(), f"Invalid hash: {INVALID_PARAM}")

self.log.info("Test the /broadcast URI")
tx = self.wallet.create_self_transfer()
resp_hex = self.test_rest_request("/broadcast", http_method='POST', req_type=ReqType.HEX, body=tx["hex"], ret_type=RetType.OBJ)
assert_equal(resp_hex.read().decode('utf-8').rstrip(), tx["txid"])
self.sync_all()
assert tx["txid"] in self.nodes[0].getrawmempool()

# Check invalid requests
resp = self.test_rest_request("/broadcast/123", http_method='POST', req_type=ReqType.HEX, body=tx["hex"], status=400, ret_type=RetType.OBJ)
assert_equal(resp.read().decode('utf-8').rstrip(), f"Invalid URI format. Expected /rest/broadcast.hex")
resp = self.test_rest_request("/broadcast", http_method="POST", req_type=ReqType.HEX, body="0000", status=400, ret_type=RetType.OBJ)
assert_equal(resp.read().decode('utf-8').rstrip(), f"TX decode failed")
resp = self.test_rest_request("/broadcast", http_method="POST", req_type=ReqType.JSON, body=tx["hex"], status=404, ret_type=RetType.OBJ)
assert_equal(resp.read().decode('utf-8').rstrip(), f"output format not found (available: hex)")

if __name__ == '__main__':
RESTTest(__file__).main()

0 comments on commit 84d99da

Please sign in to comment.