Browse Source

wallet: randomise locktime of transactions a bit. also check if stale. (#4967)

3.3.3.1
ghost43 6 years ago
committed by GitHub
parent
commit
dc19cf1fa1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      electrum/blockchain.py
  2. 3
      electrum/tests/test_wallet_vertical.py
  3. 32
      electrum/wallet.py

5
electrum/blockchain.py

@ -426,6 +426,11 @@ class Blockchain(util.PrintError):
return None return None
return deserialize_header(h, height) return deserialize_header(h, height)
def header_at_tip(self) -> Optional[dict]:
"""Return latest header."""
height = self.height()
return self.read_header(height)
def get_hash(self, height: int) -> str: def get_hash(self, height: int) -> str:
def is_height_checkpoint(): def is_height_checkpoint():
within_cp_range = height <= constants.net.max_checkpoint() within_cp_range = height <= constants.net.max_checkpoint()

3
electrum/tests/test_wallet_vertical.py

@ -1033,7 +1033,6 @@ class TestWalletSending(TestCaseForTestnet):
class NetworkMock: class NetworkMock:
relay_fee = 1000 relay_fee = 1000
def get_local_height(self): return 1325785
def run_from_another_thread(self, coro): def run_from_another_thread(self, coro):
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
return loop.run_until_complete(coro) return loop.run_until_complete(coro)
@ -1046,7 +1045,7 @@ class TestWalletSending(TestCaseForTestnet):
privkeys = ['93NQ7CFbwTPyKDJLXe97jczw33fiLijam2SCZL3Uinz1NSbHrTu', ] privkeys = ['93NQ7CFbwTPyKDJLXe97jczw33fiLijam2SCZL3Uinz1NSbHrTu', ]
network = NetworkMock() network = NetworkMock()
dest_addr = 'tb1q3ws2p0qjk5vrravv065xqlnkckvzcpclk79eu2' dest_addr = 'tb1q3ws2p0qjk5vrravv065xqlnkckvzcpclk79eu2'
tx = sweep(privkeys, network, config=None, recipient=dest_addr, fee=5000) tx = sweep(privkeys, network, config=None, recipient=dest_addr, fee=5000, locktime=1325785)
tx_copy = Transaction(tx.serialize()) tx_copy = Transaction(tx.serialize())
self.assertEqual('010000000129349e5641d79915e9d0282fdbaee8c3df0b6731bab9d70bf626e8588bde24ac010000004847304402206bf0d0a93abae0d5873a62ebf277a5dd2f33837821e8b93e74d04e19d71b578002201a6d729bc159941ef5c4c9e5fe13ece9fc544351ba531b00f68ba549c8b38a9a01fdffffff01b82e0f00000000001600148ba0a0bc12b51831f58c7ea8607e76c5982c071fd93a1400', self.assertEqual('010000000129349e5641d79915e9d0282fdbaee8c3df0b6731bab9d70bf626e8588bde24ac010000004847304402206bf0d0a93abae0d5873a62ebf277a5dd2f33837821e8b93e74d04e19d71b578002201a6d729bc159941ef5c4c9e5fe13ece9fc544351ba531b00f68ba549c8b38a9a01fdffffff01b82e0f00000000001600148ba0a0bc12b51831f58c7ea8607e76c5982c071fd93a1400',

32
electrum/wallet.py

@ -125,7 +125,8 @@ def sweep_preparations(privkeys, network: 'Network', imax=100):
return inputs, keypairs return inputs, keypairs
def sweep(privkeys, network: 'Network', config: 'SimpleConfig', recipient, fee=None, imax=100): def sweep(privkeys, network: 'Network', config: 'SimpleConfig', recipient, fee=None, imax=100,
*, locktime=None):
inputs, keypairs = sweep_preparations(privkeys, network, imax) inputs, keypairs = sweep_preparations(privkeys, network, imax)
total = sum(i.get('value') for i in inputs) total = sum(i.get('value') for i in inputs)
if fee is None: if fee is None:
@ -138,7 +139,8 @@ def sweep(privkeys, network: 'Network', config: 'SimpleConfig', recipient, fee=N
raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network))) raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network)))
outputs = [TxOutput(TYPE_ADDRESS, recipient, total - fee)] outputs = [TxOutput(TYPE_ADDRESS, recipient, total - fee)]
locktime = network.get_local_height() if locktime is None:
locktime = get_locktime_for_new_transaction(network)
tx = Transaction.from_io(inputs, outputs, locktime=locktime) tx = Transaction.from_io(inputs, outputs, locktime=locktime)
tx.set_rbf(True) tx.set_rbf(True)
@ -146,6 +148,26 @@ def sweep(privkeys, network: 'Network', config: 'SimpleConfig', recipient, fee=N
return tx return tx
def get_locktime_for_new_transaction(network: 'Network') -> int:
# if no network or not up to date, just set locktime to zero
if not network:
return 0
chain = network.blockchain()
header = chain.header_at_tip()
if not header:
return 0
STALE_DELAY = 8 * 60 * 60 # in seconds
if header['timestamp'] + STALE_DELAY < time.time():
return 0
# discourage "fee sniping"
locktime = chain.height()
# sometimes pick locktime a bit further back, to help privacy
# of setups that need more time (offline/multisig/coinjoin/...)
if random.randint(0, 9) == 0:
locktime = max(0, locktime - random.randint(0, 99))
return locktime
class CannotBumpFee(Exception): pass class CannotBumpFee(Exception): pass
@ -692,7 +714,7 @@ class Abstract_Wallet(AddressSynchronizer):
tx = Transaction.from_io(coins, outputs[:]) tx = Transaction.from_io(coins, outputs[:])
# Timelock tx to current height. # Timelock tx to current height.
tx.locktime = self.get_local_height() tx.locktime = get_locktime_for_new_transaction(self.network)
run_hook('make_unsigned_transaction', self, tx) run_hook('make_unsigned_transaction', self, tx)
return tx return tx
@ -794,7 +816,7 @@ class Abstract_Wallet(AddressSynchronizer):
continue continue
if delta > 0: if delta > 0:
raise CannotBumpFee(_('Cannot bump fee') + ': ' + _('could not find suitable outputs')) raise CannotBumpFee(_('Cannot bump fee') + ': ' + _('could not find suitable outputs'))
locktime = self.get_local_height() locktime = get_locktime_for_new_transaction(self.network)
tx_new = Transaction.from_io(inputs, outputs, locktime=locktime) tx_new = Transaction.from_io(inputs, outputs, locktime=locktime)
return tx_new return tx_new
@ -814,7 +836,7 @@ class Abstract_Wallet(AddressSynchronizer):
inputs = [item] inputs = [item]
out_address = self.get_unused_address() or address out_address = self.get_unused_address() or address
outputs = [TxOutput(TYPE_ADDRESS, out_address, value - fee)] outputs = [TxOutput(TYPE_ADDRESS, out_address, value - fee)]
locktime = self.get_local_height() locktime = get_locktime_for_new_transaction(self.network)
return Transaction.from_io(inputs, outputs, locktime=locktime) return Transaction.from_io(inputs, outputs, locktime=locktime)
def add_input_sig_info(self, txin, address): def add_input_sig_info(self, txin, address):

Loading…
Cancel
Save