Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions doc/REST-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ Given a transaction hash: returns a transaction in binary, hex-encoded binary, o

For full TX query capability, one must enable the transaction index via "txindex=1" command line / configuration option.

####Send raw transaction
`POST /rest/tx.<bin|hex|json>`

Parameter: transaction=\<signed transaction\>

Submits transaction (serialized, hex-encoded or binary) to local node and network.

Returns transaction id in binary, hex-encoded binary, or JSON formats, or an error if the transaction is invalid for any reason.

####Blocks
`GET /rest/block/<BLOCK-HASH>.<bin|hex|json>`
`GET /rest/block/notxdetails/<BLOCK-HASH>.<bin|hex|json>`
Expand Down
86 changes: 86 additions & 0 deletions qa/rpc-tests/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,5 +339,91 @@ def run_test(self):
json_obj = json.loads(json_string)
assert_equal(json_obj['bestblockhash'], bb_hash)


######################################
# POST /rest/tx/: Send raw transactions
######################################
inputs = [ {'txid' : "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout' : 1}]
outputs = { self.nodes[0].getnewaddress() : 4.99995 }
raw_tx = self.nodes[2].createrawtransaction(inputs, outputs)
raw_tx = self.nodes[2].signrawtransaction(raw_tx)

#########
# sendrawtransaction with missing input
#########
response = http_post_call(url.hostname, url.port, '/rest/tx'+self.FORMAT_SEPARATOR+"hex", raw_tx['hex'], True)
assert_equal(response.status, 400)
error_str = response.read()
assert_equal("Missing inputs" in error_str, True);

#########
# sendrawtransaction with invalid raw tx
#########
response = http_post_call(url.hostname, url.port, '/rest/tx'+self.FORMAT_SEPARATOR+"hex", "invalidrawtx", True)
assert_equal(response.status, 400)
error_str = response.read()
assert_equal("TX decode failed invalidrawtx" in error_str, True);

#########
# sendrawtransaction as hex, requesting result as hex
#########
txId = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 5)
self.sync_all()
self.nodes[0].generate(5)
self.sync_all()
inputs = [ {'txid' : txId, 'vout' : 1}]
outputs = { self.nodes[0].getnewaddress() : 4.99995 }
raw_tx = self.nodes[2].createrawtransaction(inputs, outputs)
raw_tx = self.nodes[2].signrawtransaction(raw_tx)
response = http_post_call(url.hostname, url.port, '/rest/tx'+self.FORMAT_SEPARATOR+"hex", raw_tx['hex'] , True)
assert_equal(response.status, 200)
response_str = response.read()
assert_equal(self.nodes[2].sendrawtransaction(raw_tx['hex']) in response_str, True);

self.nodes[0].generate(1)
self.sync_all()

#########
# sendrawtransaction with transaction that is already in the block chain
#########
response = http_post_call(url.hostname, url.port, '/rest/tx'+self.FORMAT_SEPARATOR+"hex", raw_tx['hex'] , True)
assert_equal(response.status, 400)
response_str = response.read()
assert_equal("Transaction already in block chain" in response_str, True);

#########
# sendrawtransaction as hex, requesting result as json
#########
txId = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 5)
inputs = [ {'txid' : txId, 'vout' : 1}]
outputs = { self.nodes[0].getnewaddress() : 4.99995 }
raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
raw_tx = self.nodes[0].signrawtransaction(raw_tx)
response = http_post_call(url.hostname, url.port, '/rest/tx'+self.FORMAT_SEPARATOR+"json", raw_tx['hex'] , True)
response_str = response.read()
assert_equal(response.status, 200)
assert_equal( "{ \"txid\" : \"" +self.nodes[0].sendrawtransaction(raw_tx['hex']) + "\" }" in response_str, True);

self.nodes[0].generate(1)
self.sync_all()
self.nodes[2].generate(2)
self.sync_all()

#########
# sendrawtransaction as binary, requesting result as binary
#########
txId = self.nodes[2].sendtoaddress(self.nodes[2].getnewaddress(), 5.0)
inputs = [ {'txid' : txId, 'vout' : 1}]
outputs = { self.nodes[2].getnewaddress() : 4.99995 }
raw_tx = self.nodes[2].createrawtransaction(inputs, outputs)
raw_tx = self.nodes[2].signrawtransaction(raw_tx)

binaryRequest = binascii.unhexlify(raw_tx['hex'])
response = http_post_call(url.hostname, url.port, '/rest/tx'+self.FORMAT_SEPARATOR+"bin", binaryRequest , True)

assert_equal(response.status, 200)
response_str = binascii.hexlify(response.read())
assert_equal(self.nodes[2].sendrawtransaction(raw_tx['hex']) in response_str, True);

if __name__ == '__main__':
RESTTest ().main ()
84 changes: 82 additions & 2 deletions src/rest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "txmempool.h"
#include "utilstrencodings.h"
#include "version.h"
#include "core_io.h"
#include "consensus/validation.h"

#include <boost/algorithm/string.hpp>
#include <boost/dynamic_bitset.hpp>
Expand Down Expand Up @@ -391,12 +393,89 @@ static bool rest_tx(HTTPRequest* req, const std::string& strURIPart)
}

default: {
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
return RESTERR(req, HTTP_NOT_FOUND, "Output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
// not reached
return true;
}


static bool rest_tx_sendrawtx(HTTPRequest* req, const std::string& strURIPart)
{
if (!CheckWarmup(req))
return false;
std::string hashStr;
const RetFormat rf = ParseDataFormat(hashStr, strURIPart);

std::string strTx = req->ReadBody();

// parse parameter
CTransaction tx;
if(rf == RF_BINARY)
{
try {
CDataStream bin(SER_NETWORK, PROTOCOL_VERSION);
bin << strTx;
bin >> tx;
}
catch (const std::exception&) {
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("TX decode failed %s",strTx));
}
}
else
{
if (!DecodeHexTx(tx, strTx))
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("TX decode failed %s",strTx));
}
uint256 hashTx = tx.GetHash();

CCoinsViewCache &view = *pcoinsTip;
const CCoins* existingCoins = view.AccessCoins(hashTx);
bool fHaveMempool = mempool.exists(hashTx);
bool fHaveChain = existingCoins && existingCoins->nHeight < 1000000000;
if (!fHaveMempool && !fHaveChain) {
// push to local node and sync with wallets
CValidationState state;
bool fMissingInputs;
if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, true)) {
if (state.IsInvalid()) {
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason()));
} else {
if (fMissingInputs) {
return RESTERR(req, HTTP_BAD_REQUEST, "Missing inputs");
}
return RESTERR(req, HTTP_BAD_REQUEST, state.GetRejectReason());
}
}
} else if (fHaveChain) {
return RESTERR(req, HTTP_BAD_REQUEST, "Transaction already in block chain");
}
RelayTransaction(tx);

switch (rf) {
case RF_BINARY: {
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, strprintf("%c", hashTx.begin()));
return true;
}
case RF_HEX: {
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, hashTx.GetHex() + "\n");
return true;
}
case RF_JSON: {
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strprintf("{ \"txid\" : \"%s\" }", hashTx.GetHex()));
return true;
}
default: {
return RESTERR(req, HTTP_BAD_REQUEST, "Output format not found (available: .bin, .hex, .json)");
}
}

// not reached
return true; // continue to process further HTTP reqs on this cxn
return true;
}

static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
Expand Down Expand Up @@ -600,6 +679,7 @@ static const struct {
bool (*handler)(HTTPRequest* req, const std::string& strReq);
} uri_prefixes[] = {
{"/rest/tx/", rest_tx},
{"/rest/tx", rest_tx_sendrawtx},
{"/rest/block/notxdetails/", rest_block_notxdetails},
{"/rest/block/", rest_block_extended},
{"/rest/chaininfo", rest_chaininfo},
Expand Down