Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] added createrawtransaction rpc #862

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
96 changes: 96 additions & 0 deletions src/qt/qrc_bitcoin_build.qrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<!DOCTYPE RCC><RCC version="1.0">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file seems to be not relevant to the changes in RPC

<qresource prefix="/icons">
<file alias="bitcoin">res/rendered_icons/bitcoin1024.png</file>
<file alias="address-book">res/icons/address-book.png</file>
<file alias="quit">res/icons/quit.png</file>
<file alias="send">res/icons/send.png</file>
<file alias="connect_0">res/icons/connect0.png</file>
<file alias="connect_1">res/icons/connect1.png</file>
<file alias="connect_2">res/icons/connect2.png</file>
<file alias="connect_3">res/icons/connect3.png</file>
<file alias="connect_4">res/icons/connect4.png</file>
<file alias="transaction_0">res/icons/transaction0.png</file>
<file alias="transaction_confirmed">res/icons/transaction2.png</file>
<file alias="transaction_conflicted">res/icons/transaction_conflicted.png</file>
<file alias="transaction_1">res/icons/clock1.png</file>
<file alias="transaction_2">res/icons/clock2.png</file>
<file alias="transaction_3">res/icons/clock3.png</file>
<file alias="transaction_4">res/icons/clock4.png</file>
<file alias="transaction_5">res/icons/clock5.png</file>
<file alias="eye">res/icons/eye.png</file>
<file alias="eye_minus">res/icons/eye_minus.png</file>
<file alias="eye_plus">res/icons/eye_plus.png</file>
<file alias="options">res/icons/configure.png</file>
<file alias="receiving_addresses">res/icons/receive.png</file>
<file alias="editpaste">res/icons/editpaste.png</file>
<file alias="editcopy">res/icons/editcopy.png</file>
<file alias="add">res/icons/add.png</file>
<file alias="edit">res/icons/edit.png</file>
<file alias="history">res/icons/history.png</file>
<file alias="overview">res/icons/overview.png</file>
<file alias="export">res/icons/export.png</file>
<file alias="synced">res/icons/synced.png</file>
<file alias="remove">res/icons/remove.png</file>
<file alias="tx_mined">res/icons/tx_mined.png</file>
<file alias="tx_input">res/icons/tx_input.png</file>
<file alias="tx_output">res/icons/tx_output.png</file>
<file alias="tx_inout">res/icons/tx_inout.png</file>
<file alias="lock_closed">res/icons/lock_closed.png</file>
<file alias="lock_open">res/icons/lock_open.png</file>
<file alias="key">res/icons/key.png</file>
<file alias="filesave">res/icons/filesave.png</file>
<file alias="debugwindow">res/icons/debugwindow.png</file>
<file alias="open">res/icons/open.png</file>
<file alias="info">res/icons/info.png</file>
<file alias="about">res/rendered_icons/about.png</file>
<file alias="about_qt">res/icons/about_qt.png</file>
<file alias="verify">res/icons/verify.png</file>
<file alias="warning">res/icons/warning.png</file>
<file alias="fontbigger">res/icons/fontbigger.png</file>
<file alias="fontsmaller">res/icons/fontsmaller.png</file>
<file alias="prompticon">res/icons/chevron.png</file>
<file alias="transaction_abandoned">res/icons/transaction_abandoned.png</file>
<file alias="hd_enabled">res/icons/hd_enabled.png</file>
<file alias="hd_disabled">res/icons/hd_disabled.png</file>
<file alias="network_disabled">res/icons/network_disabled.png</file>
<file alias="proxy">res/icons/proxy.png</file>
</qresource>
<qresource prefix="/movies">
<file alias="spinner-000">res/movies/spinner-000.png</file>
<file alias="spinner-001">res/movies/spinner-001.png</file>
<file alias="spinner-002">res/movies/spinner-002.png</file>
<file alias="spinner-003">res/movies/spinner-003.png</file>
<file alias="spinner-004">res/movies/spinner-004.png</file>
<file alias="spinner-005">res/movies/spinner-005.png</file>
<file alias="spinner-006">res/movies/spinner-006.png</file>
<file alias="spinner-007">res/movies/spinner-007.png</file>
<file alias="spinner-008">res/movies/spinner-008.png</file>
<file alias="spinner-009">res/movies/spinner-009.png</file>
<file alias="spinner-010">res/movies/spinner-010.png</file>
<file alias="spinner-011">res/movies/spinner-011.png</file>
<file alias="spinner-012">res/movies/spinner-012.png</file>
<file alias="spinner-013">res/movies/spinner-013.png</file>
<file alias="spinner-014">res/movies/spinner-014.png</file>
<file alias="spinner-015">res/movies/spinner-015.png</file>
<file alias="spinner-016">res/movies/spinner-016.png</file>
<file alias="spinner-017">res/movies/spinner-017.png</file>
<file alias="spinner-018">res/movies/spinner-018.png</file>
<file alias="spinner-019">res/movies/spinner-019.png</file>
<file alias="spinner-020">res/movies/spinner-020.png</file>
<file alias="spinner-021">res/movies/spinner-021.png</file>
<file alias="spinner-022">res/movies/spinner-022.png</file>
<file alias="spinner-023">res/movies/spinner-023.png</file>
<file alias="spinner-024">res/movies/spinner-024.png</file>
<file alias="spinner-025">res/movies/spinner-025.png</file>
<file alias="spinner-026">res/movies/spinner-026.png</file>
<file alias="spinner-027">res/movies/spinner-027.png</file>
<file alias="spinner-028">res/movies/spinner-028.png</file>
<file alias="spinner-029">res/movies/spinner-029.png</file>
<file alias="spinner-030">res/movies/spinner-030.png</file>
<file alias="spinner-031">res/movies/spinner-031.png</file>
<file alias="spinner-032">res/movies/spinner-032.png</file>
<file alias="spinner-033">res/movies/spinner-033.png</file>
<file alias="spinner-034">res/movies/spinner-034.png</file>
<file alias="spinner-035">res/movies/spinner-035.png</file>
</qresource>
</RCC>
120 changes: 120 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 {
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<unsigned char> 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)
Expand Down Expand Up @@ -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"} },
Expand Down
72 changes: 72 additions & 0 deletions test/functional/rpc_createrawtxoutputs.py
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Contributor

@dgpv dgpv Apr 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am most likely missing something, but I don't see where rawtx and signtx are assigned

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could check assets and amounts, too



if __name__ == '__main__':
RpcCreateRawTxOutputsTest().main()
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down