Skip to content

Commit d38a2c1

Browse files
committed
Merge #14890: rpc: Avoid creating non-standard raw transactions
fa4c867 rpc: Avoid creating non-standard raw transactions (MarcoFalke) Pull request description: Multiple OP_RETURN outputs in a transaction are not standard and unlikely to be relayed, so avoid creating them. Apart from that, the logic was broken in that it duplicated the same hex-data for each data output: Closes #14868. Tree-SHA512: b08d08062b5622e8a7b497e490ccaf53b06e844c863fda3bf3f932a98684a809e8341aeb98232059a795afb32d8770a6c5591a66f8e6ee372b672af245607887
2 parents 2b12268 + fa4c867 commit d38a2c1

File tree

4 files changed

+19
-16
lines changed

4 files changed

+19
-16
lines changed

src/rpc/rawtransaction.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,6 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
397397
rawTx.vin.push_back(in);
398398
}
399399

400-
std::set<CTxDestination> destinations;
401400
if (!outputs_is_obj) {
402401
// Translate array of key-value pairs into dict
403402
UniValue outputs_dict = UniValue(UniValue::VOBJ);
@@ -413,8 +412,17 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
413412
}
414413
outputs = std::move(outputs_dict);
415414
}
415+
416+
// Duplicate checking
417+
std::set<CTxDestination> destinations;
418+
bool has_data{false};
419+
416420
for (const std::string& name_ : outputs.getKeys()) {
417421
if (name_ == "data") {
422+
if (has_data) {
423+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, duplicate key: data");
424+
}
425+
has_data = true;
418426
std::vector<unsigned char> data = ParseHexV(outputs[name_].getValStr(), "Data");
419427

420428
CTxOut out(0, CScript() << OP_RETURN << data);
@@ -466,7 +474,8 @@ static UniValue createrawtransaction(const JSONRPCRequest& request)
466474
},
467475
},
468476
},
469-
{"outputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "a json array with outputs (key-value pairs).\n"
477+
{"outputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n"
478+
"That is, each address can only appear once and there can only be one 'data' object.\n"
470479
"For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
471480
" accepted as second parameter.",
472481
{
@@ -1608,7 +1617,8 @@ UniValue createpsbt(const JSONRPCRequest& request)
16081617
},
16091618
},
16101619
},
1611-
{"outputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "a json array with outputs (key-value pairs).\n"
1620+
{"outputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n"
1621+
"That is, each address can only appear once and there can only be one 'data' object.\n"
16121622
"For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
16131623
" accepted as second parameter.",
16141624
{

src/test/rpc_tests.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,6 @@ BOOST_AUTO_TEST_CASE(rpc_createraw_op_return)
129129
{
130130
BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\"}"));
131131

132-
// Allow more than one data transaction output
133-
BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\",\"data\":\"68656c6c6f776f726c64\"}"));
134-
135132
// Key not "data" (bad address)
136133
BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"somedata\":\"68656c6c6f776f726c64\"}"), std::runtime_error);
137134

src/wallet/rpcwallet.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4023,7 +4023,8 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
40234023
},
40244024
},
40254025
},
4026-
{"outputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "a json array with outputs (key-value pairs).\n"
4026+
{"outputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n"
4027+
"That is, each address can only appear once and there can only be one 'data' object.\n"
40274028
"For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
40284029
" accepted as second parameter.",
40294030
{

test/functional/rpc_rawtransaction.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ def run_test(self):
100100
assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].createrawtransaction, [], {address: -1})
101101
assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: %s" % address, self.nodes[0].createrawtransaction, [], multidict([(address, 1), (address, 1)]))
102102
assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: %s" % address, self.nodes[0].createrawtransaction, [], [{address: 1}, {address: 1}])
103+
assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", self.nodes[0].createrawtransaction, [], [{"data": 'aa'}, {"data": "bb"}])
104+
assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", self.nodes[0].createrawtransaction, [], multidict([("data", 'aa'), ("data", "bb")]))
103105
assert_raises_rpc_error(-8, "Invalid parameter, key-value pair must contain exactly one key", self.nodes[0].createrawtransaction, [], [{'a': 1, 'b': 2}])
104106
assert_raises_rpc_error(-8, "Invalid parameter, key-value pair not an object as expected", self.nodes[0].createrawtransaction, [], [['key-value pair1'], ['2']])
105107

@@ -127,19 +129,12 @@ def run_test(self):
127129
bytes_to_hex_str(tx.serialize()),
128130
self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {address2: 99}]),
129131
)
130-
# Two data outputs
131-
tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([('data', '99'), ('data', '99')])))))
132-
assert_equal(len(tx.vout), 2)
133-
assert_equal(
134-
bytes_to_hex_str(tx.serialize()),
135-
self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{'data': '99'}, {'data': '99'}]),
136-
)
137132
# Multiple mixed outputs
138-
tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), ('data', '99'), ('data', '99')])))))
133+
tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), (address2, 99), ('data', '99')])))))
139134
assert_equal(len(tx.vout), 3)
140135
assert_equal(
141136
bytes_to_hex_str(tx.serialize()),
142-
self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {'data': '99'}, {'data': '99'}]),
137+
self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {address2: 99}, {'data': '99'}]),
143138
)
144139

145140
for type in ["bech32", "p2sh-segwit", "legacy"]:

0 commit comments

Comments
 (0)