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 1d5b240 commit dbbedfe
Show file tree
Hide file tree
Showing 4 changed files with 65 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 @@ -143,6 +143,12 @@ Refer to the `getrawmempool` RPC help for details. Defaults to setting

*Query parameters for `verbose` and `mempool_sequence` available in 25.0 and up.*

#### Broadcast
`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.

Risks
-------------
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 server and wallet are started
// and reset after the RPC/REST sever and wallet are stopped.
assert(node.chainman);
assert(node.mempool);
assert(node.peerman);
Expand Down
44 changes: 44 additions & 0 deletions src/rest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "node/transaction.h"
#include <config/bitcoin-config.h> // IWYU pragma: keep

#include <rest.h>

#include "logging.h"
#include "node/types.h"
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
Expand Down Expand Up @@ -1003,6 +1006,46 @@ 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;
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");
}

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) {
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_BAD_REQUEST, "Error while broadcasting: " + err_string);
}

switch (rf) {
case RESTResponseFormat::HEX: {
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 @@ -1019,6 +1062,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
12 changes: 12 additions & 0 deletions test/functional/interface_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,5 +440,17 @@ 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
self.test_rest_request("/broadcast/123", http_method='POST', req_type=ReqType.HEX, body=tx["hex"], status=400, ret_type=RetType.OBJ)
self.test_rest_request("/broadcast", http_method="POST", req_type=ReqType.HEX, body="0000", status=400, ret_type=RetType.OBJ)
self.test_rest_request("/broadcast", http_method="GET", req_type=ReqType.HEX, status=400, ret_type=RetType.OBJ)

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

0 comments on commit dbbedfe

Please sign in to comment.