electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit dc19cf1fa1083b4236755621a36c3642bf7a3bfc
parent d5c8a0e0d0597bc510b79b76c73c26689f3d692d
Author: ghost43 <somber.night@protonmail.com>
Date:   Wed, 16 Jan 2019 18:51:59 +0100

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


Diffstat:
Melectrum/blockchain.py | 5+++++
Melectrum/tests/test_wallet_vertical.py | 3+--
Melectrum/wallet.py | 32+++++++++++++++++++++++++++-----
3 files changed, 33 insertions(+), 7 deletions(-)

diff --git a/electrum/blockchain.py b/electrum/blockchain.py @@ -426,6 +426,11 @@ class Blockchain(util.PrintError): return None 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 is_height_checkpoint(): within_cp_range = height <= constants.net.max_checkpoint() diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py @@ -1033,7 +1033,6 @@ class TestWalletSending(TestCaseForTestnet): class NetworkMock: relay_fee = 1000 - def get_local_height(self): return 1325785 def run_from_another_thread(self, coro): loop = asyncio.get_event_loop() return loop.run_until_complete(coro) @@ -1046,7 +1045,7 @@ class TestWalletSending(TestCaseForTestnet): privkeys = ['93NQ7CFbwTPyKDJLXe97jczw33fiLijam2SCZL3Uinz1NSbHrTu', ] network = NetworkMock() 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()) self.assertEqual('010000000129349e5641d79915e9d0282fdbaee8c3df0b6731bab9d70bf626e8588bde24ac010000004847304402206bf0d0a93abae0d5873a62ebf277a5dd2f33837821e8b93e74d04e19d71b578002201a6d729bc159941ef5c4c9e5fe13ece9fc544351ba531b00f68ba549c8b38a9a01fdffffff01b82e0f00000000001600148ba0a0bc12b51831f58c7ea8607e76c5982c071fd93a1400', diff --git a/electrum/wallet.py b/electrum/wallet.py @@ -125,7 +125,8 @@ def sweep_preparations(privkeys, network: 'Network', imax=100): 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) total = sum(i.get('value') for i in inputs) 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))) 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.set_rbf(True) @@ -146,6 +148,26 @@ def sweep(privkeys, network: 'Network', config: 'SimpleConfig', recipient, fee=N 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 @@ -692,7 +714,7 @@ class Abstract_Wallet(AddressSynchronizer): tx = Transaction.from_io(coins, outputs[:]) # 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) return tx @@ -794,7 +816,7 @@ class Abstract_Wallet(AddressSynchronizer): continue if delta > 0: 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) return tx_new @@ -814,7 +836,7 @@ class Abstract_Wallet(AddressSynchronizer): inputs = [item] out_address = self.get_unused_address() or address 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) def add_input_sig_info(self, txin, address):