Merge pull request #6346

627468d Add support for data-based outputs (OP_RETURN) to bitcoin-tx. (Pavel Janík)
d707853 Add OP_RETURN support in createrawtransaction RPC call, add tests. (Pavel Janík)
This commit is contained in:
Wladimir J. van der Laan 2015-08-10 17:51:03 +02:00
commit 6bb28058d3
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
7 changed files with 111 additions and 16 deletions

View File

@ -15,6 +15,8 @@ EXTRA_DIST += \
test/data/tx394b54bb.hex \ test/data/tx394b54bb.hex \
test/data/txcreate1.hex \ test/data/txcreate1.hex \
test/data/txcreate2.hex \ test/data/txcreate2.hex \
test/data/txcreatedata1.hex \
test/data/txcreatedata2.hex \
test/data/txcreatesign.hex test/data/txcreatesign.hex
JSON_TEST_FILES = \ JSON_TEST_FILES = \

View File

@ -70,6 +70,7 @@ static bool AppInitRawTx(int argc, char* argv[])
strUsage += HelpMessageOpt("locktime=N", _("Set TX lock time to N")); strUsage += HelpMessageOpt("locktime=N", _("Set TX lock time to N"));
strUsage += HelpMessageOpt("nversion=N", _("Set TX version to N")); strUsage += HelpMessageOpt("nversion=N", _("Set TX version to N"));
strUsage += HelpMessageOpt("outaddr=VALUE:ADDRESS", _("Add address-based output to TX")); strUsage += HelpMessageOpt("outaddr=VALUE:ADDRESS", _("Add address-based output to TX"));
strUsage += HelpMessageOpt("outdata=[VALUE:]DATA", _("Add data-based output to TX"));
strUsage += HelpMessageOpt("outscript=VALUE:SCRIPT", _("Add raw script output to TX")); strUsage += HelpMessageOpt("outscript=VALUE:SCRIPT", _("Add raw script output to TX"));
strUsage += HelpMessageOpt("sign=SIGHASH-FLAGS", _("Add zero or more signatures to transaction") + ". " + strUsage += HelpMessageOpt("sign=SIGHASH-FLAGS", _("Add zero or more signatures to transaction") + ". " +
_("This command requires JSON registers:") + _("This command requires JSON registers:") +
@ -231,6 +232,35 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const string& strInput)
tx.vout.push_back(txout); tx.vout.push_back(txout);
} }
static void MutateTxAddOutData(CMutableTransaction& tx, const string& strInput)
{
CAmount value = 0;
// separate [VALUE:]DATA in string
size_t pos = strInput.find(':');
if (pos==0)
throw runtime_error("TX output value not specified");
if (pos != string::npos) {
// extract and validate VALUE
string strValue = strInput.substr(0, pos);
if (!ParseMoney(strValue, value))
throw runtime_error("invalid TX output value");
}
// extract and validate DATA
string strData = strInput.substr(pos + 1, string::npos);
if (!IsHex(strData))
throw runtime_error("invalid TX output data");
std::vector<unsigned char> data = ParseHex(strData);
CTxOut txout(value, CScript() << OP_RETURN << data);
tx.vout.push_back(txout);
}
static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput) static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput)
{ {
// separate VALUE:SCRIPT in string // separate VALUE:SCRIPT in string
@ -470,6 +500,8 @@ static void MutateTx(CMutableTransaction& tx, const string& command,
MutateTxDelOutput(tx, commandVal); MutateTxDelOutput(tx, commandVal);
else if (command == "outaddr") else if (command == "outaddr")
MutateTxAddOutAddr(tx, commandVal); MutateTxAddOutAddr(tx, commandVal);
else if (command == "outdata")
MutateTxAddOutData(tx, commandVal);
else if (command == "outscript") else if (command == "outscript")
MutateTxAddOutScript(tx, commandVal); MutateTxAddOutScript(tx, commandVal);

View File

@ -318,8 +318,9 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp)
{ {
if (fHelp || params.size() != 2) if (fHelp || params.size() != 2)
throw runtime_error( throw runtime_error(
"createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,...}\n" "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,\"data\":\"hex\",...}\n"
"\nCreate a transaction spending the given inputs and sending to the given addresses.\n" "\nCreate a transaction spending the given inputs and creating new outputs.\n"
"Outputs can be addresses or data.\n"
"Returns hex-encoded raw transaction.\n" "Returns hex-encoded raw transaction.\n"
"Note that the transaction's inputs are not signed, and\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" "it is not stored in the wallet or transmitted to the network.\n"
@ -328,23 +329,25 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp)
"1. \"transactions\" (string, required) A json array of json objects\n" "1. \"transactions\" (string, required) A json array of json objects\n"
" [\n" " [\n"
" {\n" " {\n"
" \"txid\":\"id\", (string, required) The transaction id\n" " \"txid\":\"id\", (string, required) The transaction id\n"
" \"vout\":n (numeric, required) The output number\n" " \"vout\":n (numeric, required) The output number\n"
" }\n" " }\n"
" ,...\n" " ,...\n"
" ]\n" " ]\n"
"2. \"addresses\" (string, required) a json object with addresses as keys and amounts as values\n" "2. \"outputs\" (string, required) a json object with outputs\n"
" {\n" " {\n"
" \"address\": x.xxx (numeric, required) The key is the bitcoin address, the value is the " + CURRENCY_UNIT + " amount\n" " \"address\": x.xxx (numeric, required) The key is the bitcoin address, the value is the " + CURRENCY_UNIT + " amount\n"
" ,...\n" " \"data\": \"hex\", (string, required) The key is \"data\", the value is hex encoded data\n"
" ...\n"
" }\n" " }\n"
"\nResult:\n" "\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n" "\"transaction\" (string) hex string of the transaction\n"
"\nExamples\n" "\nExamples\n"
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"address\\\":0.01}\"") + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"address\\\":0.01}\"")
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"data\\\":\\\"00010203\\\"}\"")
+ HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"address\\\":0.01}\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"address\\\":0.01}\"")
+ HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"data\\\":\\\"00010203\\\"}\"")
); );
LOCK(cs_main); LOCK(cs_main);
@ -375,19 +378,27 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp)
set<CBitcoinAddress> setAddress; set<CBitcoinAddress> setAddress;
vector<string> addrList = sendTo.getKeys(); vector<string> addrList = sendTo.getKeys();
BOOST_FOREACH(const string& name_, addrList) { BOOST_FOREACH(const string& name_, addrList) {
CBitcoinAddress address(name_);
if (!address.IsValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_);
if (setAddress.count(address)) if (name_ == "data") {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_); std::vector<unsigned char> data = ParseHexV(sendTo[name_].getValStr(),"Data");
setAddress.insert(address);
CScript scriptPubKey = GetScriptForDestination(address.Get()); CTxOut out(0, CScript() << OP_RETURN << data);
CAmount nAmount = AmountFromValue(sendTo[name_]); rawTx.vout.push_back(out);
} else {
CBitcoinAddress address(name_);
if (!address.IsValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_);
CTxOut out(nAmount, scriptPubKey); if (setAddress.count(address))
rawTx.vout.push_back(out); throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_);
setAddress.insert(address);
CScript scriptPubKey = GetScriptForDestination(address.Get());
CAmount nAmount = AmountFromValue(sendTo[name_]);
CTxOut out(nAmount, scriptPubKey);
rawTx.vout.push_back(out);
}
} }
return EncodeHexTx(rawTx); return EncodeHexTx(rawTx);

View File

@ -56,5 +56,35 @@
"sign=ALL", "sign=ALL",
"outaddr=0.001:193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7"], "outaddr=0.001:193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7"],
"output_cmp": "txcreatesign.hex" "output_cmp": "txcreatesign.hex"
},
{ "exec": "./bitcoin-tx",
"args":
["-create",
"in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0",
"outdata=4:badhexdata"],
"return_code": 1
},
{ "exec": "./bitcoin-tx",
"args":
["-create",
"in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0",
"outdata=badhexdata"],
"return_code": 1
},
{ "exec": "./bitcoin-tx",
"args":
["-create",
"in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0",
"outaddr=0.18:13tuJJDR2RgArmgfv6JScSdreahzgc4T6o",
"outdata=4:54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e"],
"output_cmp": "txcreatedata1.hex"
},
{ "exec": "./bitcoin-tx",
"args":
["-create",
"in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0",
"outaddr=0.18:13tuJJDR2RgArmgfv6JScSdreahzgc4T6o",
"outdata=54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e"],
"output_cmp": "txcreatedata2.hex"
} }
] ]

View File

@ -0,0 +1 @@
01000000011f5c38dfcf6f1a5f5a87c416076d392c87e6d41970d5ad5e477a02d66bde97580000000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0084d71700000000526a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e00000000

View File

@ -0,0 +1 @@
01000000011f5c38dfcf6f1a5f5a87c416076d392c87e6d41970d5ad5e477a02d66bde97580000000000ffffffff0280a81201000000001976a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac0000000000000000526a4c4f54686973204f505f52455455524e207472616e73616374696f6e206f7574707574207761732063726561746564206279206d6f646966696564206372656174657261777472616e73616374696f6e2e00000000

View File

@ -110,6 +110,24 @@ BOOST_AUTO_TEST_CASE(rpc_rawsign)
BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true);
} }
BOOST_AUTO_TEST_CASE(rpc_createraw_op_return)
{
BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\"}"));
// Allow more than one data transaction output
BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\",\"data\":\"68656c6c6f776f726c64\"}"));
// Key not "data" (bad address)
BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"somedata\":\"68656c6c6f776f726c64\"}"), runtime_error);
// Bad hex encoding of data output
BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"12345\"}"), runtime_error);
BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"12345g\"}"), runtime_error);
// Data 81 bytes long
BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081\"}"));
}
BOOST_AUTO_TEST_CASE(rpc_format_monetary_values) BOOST_AUTO_TEST_CASE(rpc_format_monetary_values)
{ {
BOOST_CHECK(ValueFromAmount(0LL).write() == "0.00000000"); BOOST_CHECK(ValueFromAmount(0LL).write() == "0.00000000");