Skip to content

Commit

Permalink
added createrawtxoutputs rpc and test (#220)
Browse files Browse the repository at this point in the history
* Throw an exception when onboarding with a KYC file that contains invalid addresses.

* added createrawtxoutputs rpc and test

* Test for kycfile bug fix

* added createrawtxoutputs rpc and test

* Remove whitelist wallet synch changes

* Corrections to allow non-wallet builds

* Minimize split tx edge case

* updated split output test for new rpc

* added createrawtxoutputs rpc and test

* updated split output test for new rpc
  • Loading branch information
tomt1664 authored Sep 26, 2019
1 parent 3085f46 commit c71b7d8
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 21 deletions.
33 changes: 12 additions & 21 deletions qa/rpc-tests/fixedfee.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,30 +241,21 @@ def run_test (self):
inputs = []
inputs.append({"txid":txid,"vout":us_vout})
inputs.append({"txid":txid2,"vout":0})

outputs = {}
outputs[new_add1] = 2.499
outputs[new_add1] = 2.499
outputs[new_add2] = 5.5
outputs[new_add2] = 5.5
outputs["fee"] = 0.001

asset_ids = {}
asset_ids[new_add1] = issuance_tx["asset"]
asset_ids["fee"] = issuance_tx["asset"]
asset_ids[new_add2] = asset

raw_tx = self.nodes[2].createrawtransaction(inputs,outputs,0,asset_ids)

#createrawtransaction will not allow you to create a transaction sending more than one output to the same address
#so add the doubled outputs manually by splitting up the hex
doubled_outputs_tx = raw_tx[0:176] + '05' + raw_tx[178:316] + raw_tx[178:316] + raw_tx[316:454] + raw_tx[316:454] + raw_tx[454:]

signed_tx = self.nodes[2].signrawtransaction(doubled_outputs_tx)

outputs = []
outputs.append({"address":new_add1,"amount":2.499,"asset":issuance_tx["asset"]})
outputs.append({"address":new_add1,"amount":2.499,"asset":issuance_tx["asset"]})
outputs.append({"address":new_add2,"amount":5.5,"asset":asset})
outputs.append({"address":new_add2,"amount":5.5,"asset":asset})
outputs.append({"address":"fee","amount":0.001,"asset":issuance_tx["asset"]})

# create a transaction with same address outputs
rawtx = self.nodes[2].createrawtxoutputs(inputs,outputs)
signtx = self.nodes[2].signrawtransaction(rawtx)

#submit signed transaction to network
try:
txid3 = self.nodes[2].sendrawtransaction(signed_tx["hex"])
txid3 = self.nodes[2].sendrawtransaction(signtx["hex"])
except JSONRPCException as exp:
assert_equal(exp.error['code'], -26) # blocked tx spam-split outputs
else:
Expand Down
40 changes: 40 additions & 0 deletions qa/rpc-tests/rawtransactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,5 +194,45 @@ def run_test(self):
'''

# test createrawtxoutputs RPC
addr1 = self.nodes[2].getnewaddress()
addr2 = self.nodes[2].getnewaddress()
issue1 = self.nodes[2].issueasset(10.0,0,False)
issue2 = self.nodes[2].issueasset(10.0,0,False)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()

rawissue1 = self.nodes[2].getrawtransaction(issue1["txid"],True)
for vout in rawissue1["vout"]:
if vout["asset"] == issue1["asset"]: vout1 = vout["n"]
rawissue2 = self.nodes[2].getrawtransaction(issue2["txid"],True)
for vout in rawissue2["vout"]:
if vout["asset"] == issue2["asset"]: vout2 = vout["n"]

inputs = []
outputs = []
inputs.append({"txid":issue1["txid"],"vout":vout1})
inputs.append({"txid":issue2["txid"],"vout":vout2})
outputs.append({"address":addr1,"amount":10.0,"asset":issue1["asset"]})
outputs.append({"address":addr1,"amount":5.0,"asset":issue2["asset"]})
outputs.append({"address":addr2,"amount":4.99,"asset":issue2["asset"]})
outputs.append({"address":"fee","amount":0.01,"asset":issue2["asset"]})
outputs.append({"data":"deadbeef","amount":0.0,"asset":issue2["asset"]})

# create a transaction with same address outputs
rawtx = self.nodes[2].createrawtxoutputs(inputs,outputs)
signtx = self.nodes[2].signrawtransaction(rawtx)
sendtx = self.nodes[2].sendrawtransaction(signtx["hex"])

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

txdec = self.nodes[2].getrawtransaction(sendtx,True)

assert(txdec["confirmations"] == 1)
assert(len(txdec["vout"]) == 5)

if __name__ == '__main__':
RawTransactionsTest().main()
3 changes: 3 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "blindrawtransaction", 2, "ignoreblindfail" },
{ "createrawtransaction", 0, "inputs" },
{ "createrawtransaction", 1, "outputs" },
{ "createrawtxoutputs", 0, "inputs" },
{ "createrawtxoutputs", 1, "outputs" },
{ "createrawrequesttx", 0, "inputs" },
{ "createrawrequesttx", 1, "outputs" },
{ "createrawbidtx", 0, "inputs" },
Expand All @@ -116,6 +118,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "dumpissuanceblindingkey", 1, "vin" },
{ "importissuanceblindingkey", 1, "vin" },
{ "createrawtransaction", 2, "locktime" },
{ "createrawtxoutputs", 2, "locktime" },
{ "createrawtransaction", 3, "output_assetids" },
{ "createrawpolicytx", 2, "locktime" },
{ "signrawtransaction", 1, "prevtxs" },
Expand Down
114 changes: 114 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,119 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
return EncodeHexTx(rawTx);
}

UniValue createrawtxoutputs(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
throw runtime_error(
"createrawtxoutputs [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":address,\"data\":\"hex\",\"amount\":amount,\"asset\":assetID} ( locktime )\n"
"\nCreate a transaction spending the given inputs and creating specified outputs.\n"
"Outputs are explicitly sepcified as an array, including the asset ID.\n"
"Returns hex-encoded raw transaction.\n"
"Note that the transaction's inputs are not signed, and\n"
"it is not stored in the wallet or transmitted to the network.\n"

"\nArguments:\n"
"1. \"inputs\" (array, required) A json array of json objects\n"
" [\n"
" {\n"
" \"txid\":\"id\", (string, required) The transaction id\n"
" \"vout\":n, (numeric, required) The output number\n"
" \"sequence\":n (numeric, optional) The sequence number\n"
" } \n"
" ,...\n"
" ]\n"
"2. \"outputs\" (array, required) a json array of json objects for each output\n"
" {\n"
" \"address\": \"address\", (numeric or string, optional) pay to a specified address, or if \"fee\" create a fee output\n"
" \"amount\": x.xxxx, (numeric, required) value is the " + CURRENCY_UNIT + " amount of the output\n"
" \"data\": \"hex\" (string, optional) Output is OP_RETURN with the hex encoded data (alternative to address)\n"
" \"asset\": \"assetid\" (string, required) The value is the asset ID of the outputs\n"
" }\n"
" ,...\n"
" ]\n"
"3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"

"\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n"

"\nExamples:\n"
+ HelpExampleCli("createrawtxoutputs", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":\\\"myaddress\\\",\\\"amount\\\":2.34231,\\\"asset\\\":\\\"assetid\\\"}]\"")
+ HelpExampleCli("createrawtxoutputs", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"da3c69bc\\\",\\\"amount\\\":2.34231,\\\"asset\\\":\\\"assetid\\\"}]\"")
+ HelpExampleCli("createrawtxoutputs", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":\\\"myaddress\\\",\\\"amount\\\":2.34231,\\\"asset\\\":\\\"assetid\\\"},{\\\"address\\\":\\\"fee\\\",\\\"amount\\\":0.01,\\\"asset\\\":\\\"assetid\\\"}]\"")
);
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VARR)(UniValue::VARR)(UniValue::VNUM), true);
if (request.params[0].isNull() || request.params[1].isNull())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null");
UniValue inputs = request.params[0].get_array();
UniValue outputs = request.params[1].get_array();
CMutableTransaction rawTx;
if (request.params.size() > 2 && !request.params[2].isNull()) {
int64_t nLockTime = request.params[2].get_int64();
if (nLockTime < 0 || nLockTime > std::numeric_limits<uint32_t>::max())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range");
rawTx.nLockTime = nLockTime;
}

UniValue assets;

for (unsigned int idx = 0; idx < inputs.size(); idx++) {
UniValue const &input = inputs[idx];
UniValue const &o = input.get_obj();
uint256 txid = ParseHashO(o, "txid");
UniValue const &vout_v = find_value(o, "vout");
if (!vout_v.isNum())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key");
int nOutput = vout_v.get_int();
if (nOutput < 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive");
uint32_t nSequence = (rawTx.nLockTime ? std::numeric_limits<uint32_t>::max() - 1 : std::numeric_limits<uint32_t>::max());
// set the sequence number if passed in the parameters object
const UniValue& sequenceObj = find_value(o, "sequence");
if (sequenceObj.isNum()) {
int64_t seqNr64 = sequenceObj.get_int64();
if (seqNr64 < 0 || seqNr64 > std::numeric_limits<uint32_t>::max())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range");
else
nSequence = (uint32_t)seqNr64;
}
CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence);
rawTx.vin.push_back(in);
}

for (unsigned int idx = 0; idx < outputs.size(); idx++) {
UniValue const &output = outputs[idx];
UniValue const &o = output.get_obj();
uint256 assetid = ParseHashO(o, "asset");
CAsset asset(assetid);
CAmount nAmount = AmountFromValue(find_value(o, "amount"));

const UniValue& addressObj = find_value(o, "address");

if(addressObj.isStr()) {
if (addressObj.getValStr() == "fee") {
CTxOut out(asset, nAmount, CScript());
rawTx.vout.push_back(out);
} else {
CBitcoinAddress address(addressObj.getValStr());
if (!address.IsValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+addressObj.getValStr());
CScript scriptPubKey = GetScriptForDestination(address.Get());
CTxOut out(asset, nAmount, scriptPubKey);
rawTx.vout.push_back(out);
}
} else {
const UniValue& dataObj = find_value(o, "data");
if(!dataObj.isStr()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Output object without address or data key");
}
std::vector<unsigned char> data = ParseHexV(dataObj.getValStr(),"Data");
CTxOut out(asset, nAmount, CScript() << OP_RETURN << data);
rawTx.vout.push_back(out);
}
}
return EncodeHexTx(rawTx);
}

UniValue createrawpolicytx(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 4)
Expand Down Expand Up @@ -1973,6 +2086,7 @@ static const CRPCCommand commands[] =
// --------------------- ------------------------ ----------------------- ----------
{ "rawtransactions", "getrawtransaction", &getrawtransaction, true, {"txid","verbose"} },
{ "rawtransactions", "createrawtransaction", &createrawtransaction, true, {"inputs","outputs","locktime","output_assets"} },
{ "rawtransactions", "createrawtxoutputs", &createrawtxoutputs, true, {"inputs","outputs","locktime"} },
{ "rawtransactions", "createrawpolicytx", &createrawpolicytx, true, {"inputs","outputs","locktime","asset"} },
{ "rawtransactions", "createrawrequesttx", &createrawrequesttx, true, {"inputs","outputs"} },
{ "rawtransactions", "createrawbidtx", &createrawbidtx, true, {"inputs","outputs"} },
Expand Down

0 comments on commit c71b7d8

Please sign in to comment.