diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -42,11 +43,11 @@ #include /** - * High fee for sendrawtransaction and testmempoolaccept. - * By default, transaction with a fee higher than this will be rejected by the - * RPCs. This can be overridden with the maxfeerate argument. + * High fee rate for sendrawtransaction and testmempoolaccept. + * By default, transaction with a fee rate higher than this will be rejected by + * the RPCs. This can be overridden with the maxfeerate argument. */ -constexpr static Amount DEFAULT_MAX_RAW_TX_FEE{COIN / 10}; +static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10}; static void TxToJSON(const CTransaction &tx, const BlockHash &hashBlock, UniValue &entry) { @@ -897,7 +898,8 @@ {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, {"maxfeerate", RPCArg::Type::AMOUNT, - /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE), + /* default */ + FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()), "Reject transactions whose fee rate is higher than the specified " "value, expressed in " + CURRENCY_UNIT + "/kB\n"}, @@ -932,7 +934,7 @@ CTransactionRef tx(MakeTransactionRef(std::move(mtx))); - Amount max_raw_tx_fee = DEFAULT_MAX_RAW_TX_FEE; + CFeeRate max_raw_tx_fee_rate = DEFAULT_MAX_RAW_TX_FEE_RATE; // TODO: temporary migration code for old clients. Remove in v0.22 if (request.params[1].isBool()) { throw JSONRPCError(RPC_INVALID_PARAMETER, @@ -940,10 +942,12 @@ "no longer supports a boolean. To allow a " "transaction with high fees, set maxfeerate to 0."); } else if (!request.params[1].isNull()) { - size_t sz = tx->GetTotalSize(); - CFeeRate fr(AmountFromValue(request.params[1])); - max_raw_tx_fee = fr.GetFee(sz); + max_raw_tx_fee_rate = CFeeRate(AmountFromValue(request.params[1])); } + + int64_t virtual_size = GetVirtualTransactionSize(*tx); + Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); + std::string err_string; AssertLockNotHeld(cs_main); NodeContext &node = EnsureNodeContext(request.context); @@ -979,7 +983,8 @@ }, }, {"maxfeerate", RPCArg::Type::AMOUNT, - /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE), + /* default */ + FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()), "Reject transactions whose fee rate is higher than the specified " "value, expressed in " + CURRENCY_UNIT + "/kB\n"}, @@ -1030,20 +1035,20 @@ CTransactionRef tx(MakeTransactionRef(std::move(mtx))); const TxId &txid = tx->GetId(); - Amount max_raw_tx_fee = DEFAULT_MAX_RAW_TX_FEE; - // TODO: temporary migration code for old clients. Remove in v0.20 + CFeeRate max_raw_tx_fee_rate = DEFAULT_MAX_RAW_TX_FEE_RATE; + // TODO: temporary migration code for old clients. Remove in v0.22 if (request.params[1].isBool()) { 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."); } else if (!request.params[1].isNull()) { - size_t sz = tx->GetTotalSize(); - CFeeRate fr(AmountFromValue(request.params[1])); - max_raw_tx_fee = fr.GetFee(sz); + max_raw_tx_fee_rate = CFeeRate(AmountFromValue(request.params[1])); } CTxMemPool &mempool = EnsureMemPool(request.context); + int64_t virtual_size = GetVirtualTransactionSize(*tx); + Amount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); UniValue result(UniValue::VARR); UniValue result_0(UniValue::VOBJ); diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -195,6 +195,7 @@ self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True}], rawtxs=[ToHex(tx)], + maxfeerate=0, ) self.log.info('A transaction with no outputs') diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -540,6 +540,7 @@ self.log.info('sendrawtransaction/testmempoolaccept with maxfeerate') + # Test a transaction with small fee txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0) rawTx = self.nodes[0].getrawtransaction(txId, True) vout = next(o for o in rawTx['vout'] @@ -547,12 +548,12 @@ self.sync_all() inputs = [{"txid": txId, "vout": vout['n']}] - # 1000 sat fee - outputs = {self.nodes[0].getnewaddress(): Decimal("0.99999000")} + # 10000 sat fee + outputs = {self.nodes[0].getnewaddress(): Decimal("0.999990000")} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx) assert_equal(rawTxSigned['complete'], True) - # 1000 sat fee, ~200 b transaction, fee rate should land around 5 sat/b = 0.00005000 BTC/kB + # 10000 sat fee, ~200 b transaction, fee rate should land around 50 sat/b = 0.00050000 BCH/kB # Thus, testmempoolaccept should reject testres = self.nodes[2].testmempoolaccept( [rawTxSigned['hex']], 0.00001000)[0] @@ -566,11 +567,40 @@ 0.00001000) # And below calls should both succeed testres = self.nodes[2].testmempoolaccept( - rawtxs=[rawTxSigned['hex']], maxfeerate='0.00007000')[0] + rawtxs=[rawTxSigned['hex']])[0] + assert_equal(testres['allowed'], True) + self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex']) + + # Test a transaction with large fee + txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0) + rawTx = self.nodes[0].getrawtransaction(txId, True) + vout = next(o for o in rawTx['vout'] + if o['value'] == Decimal('1.00000000')) + + self.sync_all() + inputs = [{"txid": txId, "vout": vout['n']}] + # 2000000 sat fee + outputs = {self.nodes[0].getnewaddress(): Decimal("0.98000000")} + rawTx = self.nodes[2].createrawtransaction(inputs, outputs) + rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx) + assert_equal(rawTxSigned['complete'], True) + # 2000000 sat fee, ~100 b transaction, fee rate should land around 20000 sat/b = 0.20000000 BCH/kB + # Thus, testmempoolaccept should reject + testres = self.nodes[2].testmempoolaccept([rawTxSigned['hex']])[0] + assert_equal(testres['allowed'], False) + assert_equal(testres['reject-reason'], '256: absurdly-high-fee') + # and sendrawtransaction should throw + assert_raises_rpc_error(-26, + "absurdly-high-fee", + self.nodes[2].sendrawtransaction, + rawTxSigned['hex']) + # And below calls should both succeed + testres = self.nodes[2].testmempoolaccept( + rawtxs=[rawTxSigned['hex']], maxfeerate='0.20000000')[0] assert_equal(testres['allowed'], True) self.nodes[2].sendrawtransaction( hexstring=rawTxSigned['hex'], - maxfeerate='0.00007000') + maxfeerate='0.20000000') ########################################## # Decoding weird scripts in transactions # diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -514,7 +514,7 @@ raw_tx = self.nodes[0].createrawtransaction([{"txid": singletxid, "vout": 0}], { chain_addrs[0]: node0_balance / 2 - Decimal('0.01'), chain_addrs[1]: node0_balance / 2 - Decimal('0.01')}) signedtx = self.nodes[0].signrawtransactionwithwallet(raw_tx) - singletxid = self.nodes[0].sendrawtransaction(signedtx["hex"]) + singletxid = self.nodes[0].sendrawtransaction(signedtx["hex"], 0) self.nodes[0].generate(1) # Make a long chain of unconfirmed payments without hitting mempool limit