Skip to content

Commit 20802c7

Browse files
committed
wallet, rpc: add anti-fee-sniping to send and sendall
1 parent fed41b7 commit 20802c7

File tree

4 files changed

+26
-10
lines changed

4 files changed

+26
-10
lines changed

src/wallet/rpc/spend.cpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,21 @@ std::set<int> InterpretSubtractFeeFromOutputInstructions(const UniValue& sffo_in
9292
return sffo_set;
9393
}
9494

95-
static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const UniValue& options, const CMutableTransaction& rawTx)
95+
static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const UniValue& options, CMutableTransaction& rawTx)
9696
{
97+
bool can_anti_fee_snipe = !options.exists("locktime");
98+
99+
for (const CTxIn& tx_in : rawTx.vin) {
100+
// Checks sequence values consistent with DiscourageFeeSniping
101+
can_anti_fee_snipe = can_anti_fee_snipe && (tx_in.nSequence == CTxIn::MAX_SEQUENCE_NONFINAL || tx_in.nSequence == MAX_BIP125_RBF_SEQUENCE);
102+
}
103+
104+
if (can_anti_fee_snipe) {
105+
LOCK(pwallet->cs_wallet);
106+
FastRandomContext rng_fast;
107+
DiscourageFeeSniping(rawTx, rng_fast, pwallet->chain(), pwallet->GetLastBlockHash(), pwallet->GetLastBlockHeight());
108+
}
109+
97110
// Make a blank psbt
98111
PartiallySignedTransaction psbtx(rawTx);
99112

@@ -1257,7 +1270,7 @@ RPCHelpMan send()
12571270
}},
12581271
},
12591272
},
1260-
{"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
1273+
{"locktime", RPCArg::Type::NUM, RPCArg::DefaultHint{"locktime close to block height to prevent fee sniping"}, "Raw locktime. Non-0 value also locktime-activates inputs"},
12611274
{"lock_unspents", RPCArg::Type::BOOL, RPCArg::Default{false}, "Lock selected unspent outputs"},
12621275
{"psbt", RPCArg::Type::BOOL, RPCArg::DefaultHint{"automatic"}, "Always return a PSBT, implies add_to_wallet=false."},
12631276
{"subtract_fee_from_outputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Outputs to subtract the fee from, specified as integer indices.\n"
@@ -1326,7 +1339,8 @@ RPCHelpMan send()
13261339
rawTx.vout.clear();
13271340
auto txr = FundTransaction(*pwallet, rawTx, recipients, options, coin_control, /*override_min_fee=*/false);
13281341

1329-
return FinishTransaction(pwallet, options, CMutableTransaction(*txr.tx));
1342+
CMutableTransaction tx = CMutableTransaction(*txr.tx);
1343+
return FinishTransaction(pwallet, options, tx);
13301344
}
13311345
};
13321346
}
@@ -1374,7 +1388,7 @@ RPCHelpMan sendall()
13741388
},
13751389
},
13761390
},
1377-
{"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
1391+
{"locktime", RPCArg::Type::NUM, RPCArg::DefaultHint{"locktime close to block height to prevent fee sniping"}, "Raw locktime. Non-0 value also locktime-activates inputs"},
13781392
{"lock_unspents", RPCArg::Type::BOOL, RPCArg::Default{false}, "Lock selected unspent outputs"},
13791393
{"psbt", RPCArg::Type::BOOL, RPCArg::DefaultHint{"automatic"}, "Always return a PSBT, implies add_to_wallet=false."},
13801394
{"send_max", RPCArg::Type::BOOL, RPCArg::Default{false}, "When true, only use UTXOs that can pay for their own fees to maximize the output amount. When 'false' (default), no UTXO is left behind. send_max is incompatible with providing specific inputs."},

src/wallet/spend.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -936,11 +936,7 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256&
936936
return true;
937937
}
938938

939-
/**
940-
* Set a height-based locktime for new transactions (uses the height of the
941-
* current chain tip unless we are not synced with the current chain
942-
*/
943-
static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
939+
void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
944940
interfaces::Chain& chain, const uint256& block_hash, int block_height)
945941
{
946942
// All inputs must be added by now

src/wallet/spend.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@ struct CreatedTransactionResult
218218
: tx(_tx), fee(_fee), fee_calc(_fee_calc), change_pos(_change_pos) {}
219219
};
220220

221+
/**
222+
* Set a height-based locktime for new transactions (uses the height of the
223+
* current chain tip unless we are not synced with the current chain
224+
*/
225+
void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast, interfaces::Chain& chain, const uint256& block_hash, int block_height);
226+
221227
/**
222228
* Create a new transaction paying the recipients with a set of coins
223229
* selected by SelectCoins(); Also create the change output, when needed

test/functional/wallet_rescan_unconfirmed.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def run_test(self):
5353
# The only UTXO available to spend is tx_parent_to_reorg.
5454
assert_equal(len(w0_utxos), 1)
5555
assert_equal(w0_utxos[0]["txid"], tx_parent_to_reorg["txid"])
56-
tx_child_unconfirmed_sweep = w0.sendall([ADDRESS_BCRT1_UNSPENDABLE])
56+
tx_child_unconfirmed_sweep = w0.sendall(recipients=[ADDRESS_BCRT1_UNSPENDABLE], options={"locktime":0})
5757
assert tx_child_unconfirmed_sweep["txid"] in node.getrawmempool()
5858
node.syncwithvalidationinterfacequeue()
5959

0 commit comments

Comments
 (0)