diff --git a/src/qt/qrc_bitcoin_build.qrc b/src/qt/qrc_bitcoin_build.qrc new file mode 100644 index 0000000000..1c9c9ba90a --- /dev/null +++ b/src/qt/qrc_bitcoin_build.qrc @@ -0,0 +1,96 @@ + + + res/rendered_icons/bitcoin1024.png + res/icons/address-book.png + res/icons/quit.png + res/icons/send.png + res/icons/connect0.png + res/icons/connect1.png + res/icons/connect2.png + res/icons/connect3.png + res/icons/connect4.png + res/icons/transaction0.png + res/icons/transaction2.png + res/icons/transaction_conflicted.png + res/icons/clock1.png + res/icons/clock2.png + res/icons/clock3.png + res/icons/clock4.png + res/icons/clock5.png + res/icons/eye.png + res/icons/eye_minus.png + res/icons/eye_plus.png + res/icons/configure.png + res/icons/receive.png + res/icons/editpaste.png + res/icons/editcopy.png + res/icons/add.png + res/icons/edit.png + res/icons/history.png + res/icons/overview.png + res/icons/export.png + res/icons/synced.png + res/icons/remove.png + res/icons/tx_mined.png + res/icons/tx_input.png + res/icons/tx_output.png + res/icons/tx_inout.png + res/icons/lock_closed.png + res/icons/lock_open.png + res/icons/key.png + res/icons/filesave.png + res/icons/debugwindow.png + res/icons/open.png + res/icons/info.png + res/rendered_icons/about.png + res/icons/about_qt.png + res/icons/verify.png + res/icons/warning.png + res/icons/fontbigger.png + res/icons/fontsmaller.png + res/icons/chevron.png + res/icons/transaction_abandoned.png + res/icons/hd_enabled.png + res/icons/hd_disabled.png + res/icons/network_disabled.png + res/icons/proxy.png + + + res/movies/spinner-000.png + res/movies/spinner-001.png + res/movies/spinner-002.png + res/movies/spinner-003.png + res/movies/spinner-004.png + res/movies/spinner-005.png + res/movies/spinner-006.png + res/movies/spinner-007.png + res/movies/spinner-008.png + res/movies/spinner-009.png + res/movies/spinner-010.png + res/movies/spinner-011.png + res/movies/spinner-012.png + res/movies/spinner-013.png + res/movies/spinner-014.png + res/movies/spinner-015.png + res/movies/spinner-016.png + res/movies/spinner-017.png + res/movies/spinner-018.png + res/movies/spinner-019.png + res/movies/spinner-020.png + res/movies/spinner-021.png + res/movies/spinner-022.png + res/movies/spinner-023.png + res/movies/spinner-024.png + res/movies/spinner-025.png + res/movies/spinner-026.png + res/movies/spinner-027.png + res/movies/spinner-028.png + res/movies/spinner-029.png + res/movies/spinner-030.png + res/movies/spinner-031.png + res/movies/spinner-032.png + res/movies/spinner-033.png + res/movies/spinner-034.png + res/movies/spinner-035.png + + diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 1d9dd0ffab..127482829e 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -789,6 +789,125 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) return EncodeHexTx(CTransaction(rawTx)); } +UniValue createrawtxoutputs(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) + throw std::runtime_error( + "createrawtxoutputs" + "\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, { + 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::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::max() - 1 : std::numeric_limits::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::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 { + CTxDestination destination = DecodeDestination(addressObj.getValStr()); + if (!IsValidDestination(destination)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + addressObj.getValStr()); + } + CScript scriptPubKey = GetScriptForDestination(destination); + 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 data = ParseHexV(dataObj.getValStr(),"Data"); + CTxOut out(asset, nAmount, CScript() << OP_RETURN << data); + rawTx.vout.push_back(out); + } + } + return EncodeHexTx(CTransaction(rawTx)); +} + static UniValue decoderawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) @@ -3139,6 +3258,7 @@ static const CRPCCommand commands[] = // --------------------- ------------------------ ----------------------- ---------- { "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} }, { "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime","replaceable", "output_assets"} }, + { "rawtransactions", "createrawtxoutputs", &createrawtxoutputs, {"inputs","outputs","locktime"} }, { "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring","iswitness"} }, { "rawtransactions", "decodescript", &decodescript, {"hexstring"} }, { "rawtransactions", "sendrawtransaction", &sendrawtransaction, {"hexstring","allowhighfees"} }, diff --git a/test/functional/rpc_createrawtxoutputs.py b/test/functional/rpc_createrawtxoutputs.py new file mode 100755 index 0000000000..7afd516467 --- /dev/null +++ b/test/functional/rpc_createrawtxoutputs.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test transaction signing using the signrawtransaction* RPCs.""" + +from test_framework.test_framework import BitcoinTestFramework +import decimal + +class RpcCreateRawTxOutputsTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 3 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + node0,node1,node2 = self.nodes + + # 50 BTC each, rest will be 25 BTC each + node0.generate(149) + self.sync_all() + + self.do_createraw() + + def do_createraw(self): + node0,node1,node2 = self.nodes + + # test createrawtxoutputs RPC + addr1 = node2.getnewaddress() + addr2 = node2.getnewaddress() + issue1 = node2.issueasset(10.0,0,False) + issue2 = node2.issueasset(10.0,0,False) + self.sync_all() + node0.generate(10) + self.sync_all() + + rawissue1 = node2.getrawtransaction(issue1["txid"],True) + for vout in rawissue1["vout"]: + if vout["asset"] == issue1["asset"]: vout1 = vout["n"] + rawissue2 = node2.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 + node2.createrawtxoutputs(inputs,outputs) + node2.signrawtransaction(rawtx) + node2.sendrawtransaction(signtx["hex"]) + + self.sync_all() + node0.generate(1) + self.sync_all() + + txdec = node2.getrawtransaction(sendtx,True) + + assert txdec["confirmations"] == 1 + assert len(txdec["vout"]) == 5 + + +if __name__ == '__main__': + RpcCreateRawTxOutputsTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 9f5712a62a..ad9d825f42 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -169,6 +169,7 @@ 'rpc_invalidateblock.py', 'feature_rbf.py', 'mempool_packages.py', + 'rpc_createrawtxoutputs.py', 'rpc_createmultisig.py', # ELEMENTS: no versionbits in use #'feature_versionbits_warning.py',