Skip to content

Commit c033c4b

Browse files
author
MarcoFalke
committed
Merge #13541: wallet/rpc: sendrawtransaction maxfeerate
7abd2e6 wallet/rpc: add maxfeerate parameter to testmempoolaccept (Karl-Johan Alm) 6c0a6f7 wallet/rpc: add maxfeerate parameter to sendrawtransaction (Karl-Johan Alm) e5efacb test: Refactor vout fetches in rpc_rawtransaction (Karl-Johan Alm) Pull request description: This adds a new `maxfeerate` parameter to `sendrawtransaction` which forces the node to reject a transaction whose feerate is above the given fee rate. This is a safety harness from shooting yourself in the foot and accidentally overpaying fees. See also #12911. Tree-SHA512: efa50134a7c17c9330cfdfd48ba400e095c0a419cc45e630618d8b44929c25d780d1bb2710c1fbbb6e687eca373505b0338cdaa7f2ff4ca22636d84c31557a2e
2 parents 2c336a9 + 7abd2e6 commit c033c4b

15 files changed

+157
-88
lines changed

src/rpc/client.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
9292
{ "signrawtransactionwithkey", 2, "prevtxs" },
9393
{ "signrawtransactionwithwallet", 1, "prevtxs" },
9494
{ "sendrawtransaction", 1, "allowhighfees" },
95+
{ "sendrawtransaction", 1, "maxfeerate" },
9596
{ "testmempoolaccept", 0, "rawtxs" },
9697
{ "testmempoolaccept", 1, "allowhighfees" },
98+
{ "testmempoolaccept", 1, "maxfeerate" },
9799
{ "combinerawtransaction", 0, "txs" },
98100
{ "fundrawtransaction", 1, "options" },
99101
{ "fundrawtransaction", 2, "iswitness" },

src/rpc/rawtransaction.cpp

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <script/standard.h>
2929
#include <uint256.h>
3030
#include <util/bip32.h>
31+
#include <util/moneystr.h>
3132
#include <util/strencodings.h>
3233
#include <validation.h>
3334
#include <validationinterface.h>
@@ -1039,7 +1040,7 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request)
10391040
"\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n",
10401041
{
10411042
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
1042-
{"allowhighfees", RPCArg::Type::BOOL, /* default */ "false", "Allow high fees"},
1043+
{"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(maxTxFee), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
10431044
},
10441045
RPCResult{
10451046
"\"hex\" (string) The transaction hash in hex\n"
@@ -1056,20 +1057,35 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request)
10561057
},
10571058
}.ToString());
10581059

1059-
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL});
1060+
RPCTypeCheck(request.params, {
1061+
UniValue::VSTR,
1062+
UniValueType(), // NUM or BOOL, checked later
1063+
});
10601064

10611065
// parse hex string from parameter
10621066
CMutableTransaction mtx;
10631067
if (!DecodeHexTx(mtx, request.params[0].get_str()))
10641068
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
10651069
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
10661070

1067-
bool allowhighfees = false;
1068-
if (!request.params[1].isNull()) allowhighfees = request.params[1].get_bool();
1069-
const CAmount highfee{allowhighfees ? 0 : ::maxTxFee};
1071+
CAmount max_raw_tx_fee = maxTxFee;
1072+
// TODO: temporary migration code for old clients. Remove in v0.20
1073+
if (request.params[1].isBool()) {
1074+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Second argument must be numeric (maxfeerate) and no longer supports a boolean. To allow a transaction with high fees, set maxfeerate to 0.");
1075+
} else if (request.params[1].isNum()) {
1076+
size_t weight = GetTransactionWeight(*tx);
1077+
CFeeRate fr(AmountFromValue(request.params[1]));
1078+
// the +3/4 part rounds the value up, and is the same formula used when
1079+
// calculating the fee for a transaction
1080+
// (see GetVirtualTransactionSize)
1081+
max_raw_tx_fee = fr.GetFee((weight+3)/4);
1082+
} else if (!request.params[1].isNull()) {
1083+
throw JSONRPCError(RPC_INVALID_PARAMETER, "second argument (maxfeerate) must be numeric");
1084+
}
1085+
10701086
uint256 txid;
10711087
std::string err_string;
1072-
const TransactionError err = BroadcastTransaction(tx, txid, err_string, highfee);
1088+
const TransactionError err = BroadcastTransaction(tx, txid, err_string, max_raw_tx_fee);
10731089
if (TransactionError::OK != err) {
10741090
throw JSONRPCTransactionError(err, err_string);
10751091
}
@@ -1092,7 +1108,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
10921108
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
10931109
},
10941110
},
1095-
{"allowhighfees", RPCArg::Type::BOOL, /* default */ "false", "Allow high fees"},
1111+
{"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(maxTxFee), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
10961112
},
10971113
RPCResult{
10981114
"[ (array) The result of the mempool acceptance test for each raw transaction in the input array.\n"
@@ -1117,7 +1133,11 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
11171133
}.ToString());
11181134
}
11191135

1120-
RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VBOOL});
1136+
RPCTypeCheck(request.params, {
1137+
UniValue::VARR,
1138+
UniValueType(), // NUM or BOOL, checked later
1139+
});
1140+
11211141
if (request.params[0].get_array().size() != 1) {
11221142
throw JSONRPCError(RPC_INVALID_PARAMETER, "Array must contain exactly one raw transaction for now");
11231143
}
@@ -1129,9 +1149,19 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
11291149
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
11301150
const uint256& tx_hash = tx->GetHash();
11311151

1132-
CAmount max_raw_tx_fee = ::maxTxFee;
1133-
if (!request.params[1].isNull() && request.params[1].get_bool()) {
1134-
max_raw_tx_fee = 0;
1152+
CAmount max_raw_tx_fee = maxTxFee;
1153+
// TODO: temporary migration code for old clients. Remove in v0.20
1154+
if (request.params[1].isBool()) {
1155+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Second argument must be numeric (maxfeerate) and no longer supports a boolean. To allow a transaction with high fees, set maxfeerate to 0.");
1156+
} else if (request.params[1].isNum()) {
1157+
size_t weight = GetTransactionWeight(*tx);
1158+
CFeeRate fr(AmountFromValue(request.params[1]));
1159+
// the +3/4 part rounds the value up, and is the same formula used when
1160+
// calculating the fee for a transaction
1161+
// (see GetVirtualTransactionSize)
1162+
max_raw_tx_fee = fr.GetFee((weight+3)/4);
1163+
} else if (!request.params[1].isNull()) {
1164+
throw JSONRPCError(RPC_INVALID_PARAMETER, "second argument (maxfeerate) must be numeric");
11351165
}
11361166

11371167
UniValue result(UniValue::VARR);
@@ -2048,11 +2078,11 @@ static const CRPCCommand commands[] =
20482078
{ "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime","replaceable"} },
20492079
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring","iswitness"} },
20502080
{ "rawtransactions", "decodescript", &decodescript, {"hexstring"} },
2051-
{ "rawtransactions", "sendrawtransaction", &sendrawtransaction, {"hexstring","allowhighfees"} },
2081+
{ "rawtransactions", "sendrawtransaction", &sendrawtransaction, {"hexstring","allowhighfees|maxfeerate"} },
20522082
{ "rawtransactions", "combinerawtransaction", &combinerawtransaction, {"txs"} },
20532083
{ "hidden", "signrawtransaction", &signrawtransaction, {"hexstring","prevtxs","privkeys","sighashtype"} },
20542084
{ "rawtransactions", "signrawtransactionwithkey", &signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} },
2055-
{ "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees"} },
2085+
{ "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees|maxfeerate"} },
20562086
{ "rawtransactions", "decodepsbt", &decodepsbt, {"psbt"} },
20572087
{ "rawtransactions", "combinepsbt", &combinepsbt, {"txs"} },
20582088
{ "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} },

src/validation.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3159,6 +3159,26 @@ static int GetWitnessCommitmentIndex(const CBlock& block)
31593159
return commitpos;
31603160
}
31613161

3162+
// Compute at which vout of the block's coinbase transaction the signet
3163+
// signature occurs, or -1 if not found.
3164+
static int GetSignetSignatureIndex(const CBlock& block)
3165+
{
3166+
if (!block.vtx.empty()) {
3167+
for (size_t o = 0; o < block.vtx[0]->vout.size(); o++) {
3168+
if (block.vtx[0]->vout[o].scriptPubKey.size() >= 68 // at minimum 64 byte signature plus script/header data
3169+
&& block.vtx[0]->vout[o].scriptPubKey[0] == OP_RETURN // unspendable
3170+
&& block.vtx[0]->vout[o].scriptPubKey[1] == block.vtx[0]->vout[o].scriptPubKey.size() - 1 // push the rest
3171+
&& block.vtx[0]->vout[o].scriptPubKey[2] == 0xec // 's' ^ 0x9f
3172+
&& block.vtx[0]->vout[o].scriptPubKey[3] == 0xc7 // 'i' ^ 0xae
3173+
&& block.vtx[0]->vout[o].scriptPubKey[4] == 0xda // 'g' ^ 0xbd
3174+
&& block.vtx[0]->vout[o].scriptPubKey[5] == 0xa2) { // 'n' ^ 0xcc
3175+
return (int)o;
3176+
}
3177+
}
3178+
}
3179+
return -1;
3180+
}
3181+
31623182
void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams)
31633183
{
31643184
int commitpos = GetWitnessCommitmentIndex(block);

test/functional/feature_cltv.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def run_test(self):
113113
# rejected from the mempool for exactly that reason.
114114
assert_equal(
115115
[{'txid': spendtx.hash, 'allowed': False, 'reject-reason': '64: non-mandatory-script-verify-flag (Negative locktime)'}],
116-
self.nodes[0].testmempoolaccept(rawtxs=[spendtx.serialize().hex()], allowhighfees=True)
116+
self.nodes[0].testmempoolaccept(rawtxs=[spendtx.serialize().hex()], maxfeerate=0)
117117
)
118118

119119
# Now we verify that a block with this transaction is also invalid.

test/functional/feature_dersig.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def run_test(self):
102102
# rejected from the mempool for exactly that reason.
103103
assert_equal(
104104
[{'txid': spendtx.hash, 'allowed': False, 'reject-reason': '64: non-mandatory-script-verify-flag (Non-canonical DER signature)'}],
105-
self.nodes[0].testmempoolaccept(rawtxs=[spendtx.serialize().hex()], allowhighfees=True)
105+
self.nodes[0].testmempoolaccept(rawtxs=[spendtx.serialize().hex()], maxfeerate=0)
106106
)
107107

108108
# Now we verify that a block with this transaction is also invalid.

test/functional/feature_fee_estimation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee
6565
# the ScriptSig that will satisfy the ScriptPubKey.
6666
for inp in tx.vin:
6767
inp.scriptSig = SCRIPT_SIG[inp.prevout.n]
68-
txid = from_node.sendrawtransaction(ToHex(tx), True)
68+
txid = from_node.sendrawtransaction(hexstring=ToHex(tx), maxfeerate=0)
6969
unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee})
7070
unconflist.append({"txid": txid, "vout": 1, "amount": amount})
7171

@@ -95,7 +95,7 @@ def split_inputs(from_node, txins, txouts, initial_split=False):
9595
else:
9696
tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]]
9797
completetx = ToHex(tx)
98-
txid = from_node.sendrawtransaction(completetx, True)
98+
txid = from_node.sendrawtransaction(hexstring=completetx, maxfeerate=0)
9999
txouts.append({"txid": txid, "vout": 0, "amount": half_change})
100100
txouts.append({"txid": txid, "vout": 1, "amount": rem_change})
101101

test/functional/feature_nulldummy.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,17 @@ def run_test(self):
6464

6565
self.log.info("Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [430]")
6666
test1txs = [create_transaction(self.nodes[0], coinbase_txid[0], self.ms_address, amount=49)]
67-
txid1 = self.nodes[0].sendrawtransaction(test1txs[0].serialize_with_witness().hex(), True)
67+
txid1 = self.nodes[0].sendrawtransaction(test1txs[0].serialize_with_witness().hex(), 0)
6868
test1txs.append(create_transaction(self.nodes[0], txid1, self.ms_address, amount=48))
69-
txid2 = self.nodes[0].sendrawtransaction(test1txs[1].serialize_with_witness().hex(), True)
69+
txid2 = self.nodes[0].sendrawtransaction(test1txs[1].serialize_with_witness().hex(), 0)
7070
test1txs.append(create_transaction(self.nodes[0], coinbase_txid[1], self.wit_ms_address, amount=49))
71-
txid3 = self.nodes[0].sendrawtransaction(test1txs[2].serialize_with_witness().hex(), True)
71+
txid3 = self.nodes[0].sendrawtransaction(test1txs[2].serialize_with_witness().hex(), 0)
7272
self.block_submit(self.nodes[0], test1txs, False, True)
7373

7474
self.log.info("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation")
7575
test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, amount=47)
7676
trueDummy(test2tx)
77-
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize_with_witness().hex(), True)
77+
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize_with_witness().hex(), 0)
7878

7979
self.log.info("Test 3: Non-NULLDUMMY base transactions should be accepted in a block before activation [431]")
8080
self.block_submit(self.nodes[0], [test2tx], False, True)
@@ -83,19 +83,19 @@ def run_test(self):
8383
test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, amount=46)
8484
test6txs = [CTransaction(test4tx)]
8585
trueDummy(test4tx)
86-
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize_with_witness().hex(), True)
86+
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize_with_witness().hex(), 0)
8787
self.block_submit(self.nodes[0], [test4tx])
8888

8989
self.log.info("Test 5: Non-NULLDUMMY P2WSH multisig transaction invalid after activation")
9090
test5tx = create_transaction(self.nodes[0], txid3, self.wit_address, amount=48)
9191
test6txs.append(CTransaction(test5tx))
9292
test5tx.wit.vtxinwit[0].scriptWitness.stack[0] = b'\x01'
93-
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test5tx.serialize_with_witness().hex(), True)
93+
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test5tx.serialize_with_witness().hex(), 0)
9494
self.block_submit(self.nodes[0], [test5tx], True)
9595

9696
self.log.info("Test 6: NULLDUMMY compliant base/witness transactions should be accepted to mempool and in block after activation [432]")
9797
for i in test6txs:
98-
self.nodes[0].sendrawtransaction(i.serialize_with_witness().hex(), True)
98+
self.nodes[0].sendrawtransaction(i.serialize_with_witness().hex(), 0)
9999
self.block_submit(self.nodes[0], test6txs, True, True)
100100

101101
def block_submit(self, node, txs, witness=False, accept=False):

0 commit comments

Comments
 (0)