Skip to content

Commit

Permalink
[wallet]: enforce -maxfeerate on wallet transactions
Browse files Browse the repository at this point in the history
- This commit prevents the wallet from creating transactions
  above `maxfeerate`, and tests the new functionality.
  • Loading branch information
ismaelsadeeq committed Feb 13, 2024
1 parent 0ad2f2c commit d0507d2
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 1 deletion.
7 changes: 7 additions & 0 deletions src/wallet/feebumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CMutableTrans
return feebumper::Result::WALLET_ERROR;
}

// check that new fee rate does not exceed maxfeerate
if (newFeerate > wallet.m_max_tx_fee_rate) {
errors.push_back(strprintf(Untranslated("New fee rate %s is too high (cannot be higher than -maxfeerate %s)"),
FormatMoney(newFeerate.GetFeePerK()), FormatMoney(wallet.m_max_tx_fee_rate.GetFeePerK())));
return feebumper::Result::WALLET_ERROR;
}

std::vector<COutPoint> reused_inputs;
reused_inputs.reserve(mtx.vin.size());
for (const CTxIn& txin : mtx.vin) {
Expand Down
3 changes: 3 additions & 0 deletions src/wallet/rpc/spend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,9 @@ RPCHelpMan sendall()
if (fee_from_size > pwallet->m_max_tx_fee) {
throw JSONRPCError(RPC_WALLET_ERROR, TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED).original);
}
if (CFeeRate(fee_from_size, tx_size.vsize) > pwallet->m_max_tx_fee_rate) {
throw JSONRPCError(RPC_WALLET_ERROR, TransactionErrorString(TransactionError::MAX_FEE_RATE_EXCEEDED).original);
}

if (effective_value <= 0) {
if (send_max) {
Expand Down
5 changes: 5 additions & 0 deletions src/wallet/spend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,11 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
return util::Error{TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED)};
}

CFeeRate tx_fee_rate = CFeeRate(current_fee, nBytes);
if (tx_fee_rate > wallet.m_max_tx_fee_rate) {
return util::Error{TransactionErrorString(TransactionError::MAX_FEE_RATE_EXCEEDED)};
}

if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
auto result = wallet.chain().checkChainLimits(tx);
Expand Down
8 changes: 7 additions & 1 deletion test/functional/wallet_bumpfee.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,11 @@ def test_invalid_parameters(self, rbf_node, peer_node, dest_address):
assert_raises_rpc_error(-8, "Insufficient total fee 0.00000141", rbf_node.bumpfee, rbfid, fee_rate=INSUFFICIENT)

self.log.info("Test invalid fee rate settings")
assert_raises_rpc_error(-4, "Specified or calculated fee 0.141 is too high (cannot be higher than -maxtxfee 0.10",

# Bumping to a very high fee rate above the default -maxfeerate should fail
assert_raises_rpc_error(-4, "New fee rate 1.00 is too high (cannot be higher than -maxfeerate 0.10)",
rbf_node.bumpfee, rbfid, fee_rate=TOO_HIGH)

# Test fee_rate with zero values.
msg = "Insufficient total fee 0.00"
for zero_value in [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]:
Expand Down Expand Up @@ -563,6 +566,9 @@ def test_maxtxfee_fails(self, rbf_node, dest_address):
rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
rbfid = spend_one_input(rbf_node, dest_address)
assert_raises_rpc_error(-4, "Unable to create transaction. Fee exceeds maximum configured by user (maxtxfee)", rbf_node.bumpfee, rbfid)

# When user passed fee rate causes base fee to be above maxtxfee we fail early
assert_raises_rpc_error(-4, "Specified or calculated fee 0.0000282 is too high (cannot be higher than -maxtxfee 0.000025)", rbf_node.bumpfee, rbfid, fee_rate=20)
self.restart_node(1, self.extra_args[1])
rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
self.connect_nodes(1, 0)
Expand Down
25 changes: 25 additions & 0 deletions test/functional/wallet_send.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,30 @@ def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,

return res

def test_maxfeerate(self):
self.log.info("test -maxfeerate enforcement on wallet transactions.")
# Default maxfeerate is 10,000 sats/vb
# Wallet will reject all transactions with feerate above 10,000 sats/vb.
assert_raises_rpc_error(-6, "Fee rate exceeds maximum configured by user (maxfeerate)",
self.nodes[0].sendtoaddress, address=self.nodes[0].getnewaddress(), amount=1, fee_rate=10001)

# All transaction with feerate <= 10,000 sats/vb can be created.
self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), amount=1, fee_rate=9900)

# Configure a lower -maxfeerate of 10 sats/vb.
self.restart_node(0, extra_args=['-maxfeerate=0.00010'])

# Wallet will reject all transactions with fee rate above 10 sats/vb.
assert_raises_rpc_error(-6, "Fee rate exceeds maximum configured by user (maxfeerate)",
self.nodes[0].sendtoaddress, address=self.nodes[0].getnewaddress(), amount=1, fee_rate=11)

# Fee rates <= 10 sats/vb will be accepted by the wallet.
self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), amount=1, fee_rate=9)

# Restart the node with the default -maxfeerate option
self.restart_node(0)


def run_test(self):
self.log.info("Setup wallets...")
# w0 is a wallet with coinbase rewards
Expand Down Expand Up @@ -575,6 +599,7 @@ def run_test(self):
testres = self.nodes[0].testmempoolaccept([signed["hex"]])[0]
assert_equal(testres["allowed"], True)
assert_fee_amount(testres["fees"]["base"], testres["vsize"], Decimal(0.0001))
self.test_maxfeerate()

if __name__ == '__main__':
WalletSendTest().main()

0 comments on commit d0507d2

Please sign in to comment.