mirror of
https://github.com/dogecoin/dogecoin.git
synced 2026-01-31 10:30:52 +00:00
wallet: Avoid leaking locktime fingerprint when anti-fee-sniping
Cherry-picked from: fa48baf23 Conflicts: - Missing chain().lock(), moved assignment until after locking cs_main. - Different structure and version of test framework required: - Different block heights - Different way of documenting test steps - Removal of wallet check, our tests don't run without a wallet
This commit is contained in:
parent
a6ac45c135
commit
cd0444871e
@ -166,6 +166,7 @@ testScripts = [
|
||||
'p2p-leaktests.py',
|
||||
'replace-by-fee.py',
|
||||
'p2p-policy.py',
|
||||
'wallet_create_tx.py',
|
||||
]
|
||||
if ENABLE_ZMQ:
|
||||
testScripts.append('zmq_test.py')
|
||||
|
||||
32
qa/rpc-tests/wallet_create_tx.py
Executable file
32
qa/rpc-tests/wallet_create_tx.py
Executable file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2018 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
)
|
||||
|
||||
|
||||
class CreateTxWalletTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = False
|
||||
self.num_nodes = 1
|
||||
|
||||
def run_test(self):
|
||||
# Check that we have some (old) blocks and that anti-fee-sniping is disabled
|
||||
assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 120)
|
||||
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
|
||||
tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex'])
|
||||
assert_equal(tx['locktime'], 0)
|
||||
|
||||
# Check that anti-fee-sniping is enabled when we mine a recent block
|
||||
self.nodes[0].generate(1)
|
||||
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
|
||||
tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex'])
|
||||
assert 0 < tx['locktime'] <= 121
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
CreateTxWalletTest().main()
|
||||
@ -2417,6 +2417,66 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsCurrentForAntiFeeSniping()
|
||||
{
|
||||
if (IsInitialBlockDownload()) {
|
||||
return false;
|
||||
}
|
||||
constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60; // in seconds
|
||||
if (chainActive.Tip()->GetBlockTime() < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a height-based locktime for new transactions (uses the height of the
|
||||
* current chain tip unless we are not synced with the current chain
|
||||
*/
|
||||
static uint32_t GetLocktimeForNewTransaction()
|
||||
{
|
||||
uint32_t locktime;
|
||||
// Discourage fee sniping.
|
||||
//
|
||||
// For a large miner the value of the transactions in the best block and
|
||||
// the mempool can exceed the cost of deliberately attempting to mine two
|
||||
// blocks to orphan the current best block. By setting nLockTime such that
|
||||
// only the next block can include the transaction, we discourage this
|
||||
// practice as the height restricted and limited blocksize gives miners
|
||||
// considering fee sniping fewer options for pulling off this attack.
|
||||
//
|
||||
// A simple way to think about this is from the wallet's point of view we
|
||||
// always want the blockchain to move forward. By setting nLockTime this
|
||||
// way we're basically making the statement that we only want this
|
||||
// transaction to appear in the next block; we don't want to potentially
|
||||
// encourage reorgs by allowing transactions to appear at lower heights
|
||||
// than the next block in forks of the best chain.
|
||||
//
|
||||
// Of course, the subsidy is high enough, and transaction volume low
|
||||
// enough, that fee sniping isn't a problem yet, but by implementing a fix
|
||||
// now we ensure code won't be written that makes assumptions about
|
||||
// nLockTime that preclude a fix later.
|
||||
if (IsCurrentForAntiFeeSniping()) {
|
||||
locktime = chainActive.Height();
|
||||
|
||||
// Secondly occasionally randomly pick a nLockTime even further back, so
|
||||
// that transactions that are delayed after signing for whatever reason,
|
||||
// e.g. high-latency mix networks and some CoinJoin implementations, have
|
||||
// better privacy.
|
||||
if (GetRandInt(10) == 0) {
|
||||
locktime = std::max(0, (int)locktime - GetRandInt(100));
|
||||
}
|
||||
} else {
|
||||
// If our chain is lagging behind, we can't discourage fee sniping nor help
|
||||
// the privacy of high-latency transactions. To avoid leaking a potentially
|
||||
// unique "nLockTime fingerprint", set nLockTime to a constant.
|
||||
locktime = 0;
|
||||
}
|
||||
assert(locktime <= (unsigned int)chainActive.Height());
|
||||
assert(locktime < LOCKTIME_THRESHOLD);
|
||||
return locktime;
|
||||
}
|
||||
|
||||
bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
|
||||
int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
|
||||
{
|
||||
@ -2445,42 +2505,12 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
|
||||
wtxNew.BindWallet(this);
|
||||
CMutableTransaction txNew;
|
||||
|
||||
// Discourage fee sniping.
|
||||
//
|
||||
// For a large miner the value of the transactions in the best block and
|
||||
// the mempool can exceed the cost of deliberately attempting to mine two
|
||||
// blocks to orphan the current best block. By setting nLockTime such that
|
||||
// only the next block can include the transaction, we discourage this
|
||||
// practice as the height restricted and limited blocksize gives miners
|
||||
// considering fee sniping fewer options for pulling off this attack.
|
||||
//
|
||||
// A simple way to think about this is from the wallet's point of view we
|
||||
// always want the blockchain to move forward. By setting nLockTime this
|
||||
// way we're basically making the statement that we only want this
|
||||
// transaction to appear in the next block; we don't want to potentially
|
||||
// encourage reorgs by allowing transactions to appear at lower heights
|
||||
// than the next block in forks of the best chain.
|
||||
//
|
||||
// Of course, the subsidy is high enough, and transaction volume low
|
||||
// enough, that fee sniping isn't a problem yet, but by implementing a fix
|
||||
// now we ensure code won't be written that makes assumptions about
|
||||
// nLockTime that preclude a fix later.
|
||||
txNew.nLockTime = chainActive.Height();
|
||||
|
||||
// Secondly occasionally randomly pick a nLockTime even further back, so
|
||||
// that transactions that are delayed after signing for whatever reason,
|
||||
// e.g. high-latency mix networks and some CoinJoin implementations, have
|
||||
// better privacy.
|
||||
if (GetRandInt(10) == 0)
|
||||
txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
|
||||
|
||||
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
|
||||
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
|
||||
|
||||
{
|
||||
set<pair<const CWalletTx*,unsigned int> > setCoins;
|
||||
LOCK2(cs_main, cs_wallet);
|
||||
{
|
||||
txNew.nLockTime = GetLocktimeForNewTransaction();
|
||||
|
||||
std::vector<COutput> vAvailableCoins;
|
||||
AvailableCoins(vAvailableCoins, true, coinControl);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user