From b1e5d9883de73abe6cdb59ddc4ed50e975e0a6fa Mon Sep 17 00:00:00 2001 From: Cryp Toon Date: Tue, 12 Mar 2024 22:24:51 +0100 Subject: [PATCH] Use anti-fee-snipping and set locktime to current block+1 by default in wallets --- bitcoinlib/db.py | 1 + bitcoinlib/transactions.py | 4 ++-- bitcoinlib/wallets.py | 34 ++++++++++++++++++++++++---------- tests/bitcoinlib_encrypted.db | Bin 139264 -> 139264 bytes 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/bitcoinlib/db.py b/bitcoinlib/db.py index 5a7089de..ba8486bb 100644 --- a/bitcoinlib/db.py +++ b/bitcoinlib/db.py @@ -248,6 +248,7 @@ class DbWallet(Base): "* If accounts are used, the account level must be 3. I.e.: m/purpose/coin_type/account/ " "* All keys must be hardened, except for change, address_index or cosigner_id " " Max length of path is 8 levels") + anti_fee_snipping = Column(Boolean, default=True, doc="Set default locktime in transactions to avoid fee-snipping") default_account_id = Column(Integer, doc="ID of default account for this wallet if multiple accounts are used") __table_args__ = ( diff --git a/bitcoinlib/transactions.py b/bitcoinlib/transactions.py index e3372d3c..95741430 100644 --- a/bitcoinlib/transactions.py +++ b/bitcoinlib/transactions.py @@ -1089,9 +1089,9 @@ def __init__(self, inputs=None, outputs=None, locktime=0, version=None, :type version: bytes, int :param network: Network, leave empty for default network :type network: str, Network - :param fee: Fee in smallest denominator (ie Satoshi) for complete transaction + :param fee: Fee in the smallest denominator (ie Satoshi) for complete transaction :type fee: int - :param fee_per_kb: Fee in smallest denominator per kilobyte. Specify when exact transaction size is not known. + :param fee_per_kb: Fee in the smallest denominator per kilobyte. Specify when exact transaction size is not known. :type fee_per_kb: int :param size: Transaction size in bytes :type size: int diff --git a/bitcoinlib/wallets.py b/bitcoinlib/wallets.py index 4f77571d..bb65ac93 100644 --- a/bitcoinlib/wallets.py +++ b/bitcoinlib/wallets.py @@ -874,7 +874,8 @@ def store(self): wallet_id=self.hdwallet.wallet_id, txid=bytes.fromhex(self.txid), block_height=self.block_height, size=self.size, confirmations=self.confirmations, date=self.date, fee=self.fee, status=self.status, input_total=self.input_total, output_total=self.output_total, network_name=self.network.name, - raw=self.rawtx, verified=self.verified, account_id=self.account_id) + raw=self.rawtx, verified=self.verified, account_id=self.account_id, locktime=self.locktime, + version=self.version_int, coinbase=self.coinbase, index=self.index) sess.add(new_tx) self.hdwallet._commit() txidn = new_tx.id @@ -890,6 +891,7 @@ def store(self): db_tx.network_name = self.network.name if self.network.name else db_tx.name db_tx.raw = self.rawtx if self.rawtx else db_tx.raw db_tx.verified = self.verified + db_tx.locktime = self.locktime self.hdwallet._commit() assert txidn @@ -1041,8 +1043,8 @@ class Wallet(object): @classmethod def _create(cls, name, key, owner, network, account_id, purpose, scheme, parent_id, sort_keys, - witness_type, encoding, multisig, sigs_required, cosigner_id, key_path, db_uri, db_cache_uri, - db_password): + witness_type, encoding, multisig, sigs_required, cosigner_id, key_path, + anti_fee_snipping, db_uri, db_cache_uri, db_password): db = Db(db_uri, db_password) session = db.session @@ -1082,7 +1084,7 @@ def _create(cls, name, key, owner, network, account_id, purpose, scheme, parent_ new_wallet = DbWallet(name=name, owner=owner, network_name=network, purpose=purpose, scheme=scheme, sort_keys=sort_keys, witness_type=witness_type, parent_id=parent_id, encoding=encoding, multisig=multisig, multisig_n_required=sigs_required, cosigner_id=cosigner_id, - key_path=key_path) + key_path=key_path, anti_fee_snipping=anti_fee_snipping) session.add(new_wallet) session.commit() new_wallet_id = new_wallet.id @@ -1121,7 +1123,8 @@ def _commit(self): @classmethod def create(cls, name, keys=None, owner='', network=None, account_id=0, purpose=0, scheme='bip32', sort_keys=True, password='', witness_type=None, encoding=None, multisig=None, sigs_required=None, - cosigner_id=None, key_path=None, db_uri=None, db_cache_uri=None, db_password=None): + cosigner_id=None, key_path=None, anti_fee_snipping=True, db_uri=None, db_cache_uri=None, + db_password=None): """ Create Wallet and insert in database. Generate masterkey or import key when specified. @@ -1192,6 +1195,8 @@ def create(cls, name, keys=None, owner='', network=None, account_id=0, purpose=0 * All keys must be hardened, except for change, address_index or cosigner_id * Max length of path is 8 levels :type key_path: list, str + :param anti_fee_snipping: Set default locktime in transactions as current block height + 1 to avoid fee-snipping. Default is True + :type anti_fee_snipping: boolean :param db_uri: URI of the database for wallets, wallet transactions and keys :type db_uri: str :param db_cache_uri: URI of the cache database. If not specified the default cache database is used when using sqlite, for other databasetypes the cache database is merged with the wallet database (db_uri) @@ -1310,7 +1315,8 @@ def create(cls, name, keys=None, owner='', network=None, account_id=0, purpose=0 hdpm = cls._create(name, key, owner=owner, network=network, account_id=account_id, purpose=purpose, scheme=scheme, parent_id=None, sort_keys=sort_keys, witness_type=witness_type, encoding=encoding, multisig=multisig, sigs_required=sigs_required, cosigner_id=cosigner_id, - key_path=main_key_path, db_uri=db_uri, db_cache_uri=db_cache_uri, db_password=db_password) + anti_fee_snipping=anti_fee_snipping, key_path=main_key_path, db_uri=db_uri, + db_cache_uri=db_cache_uri, db_password=db_password) if multisig: wlt_cos_id = 0 @@ -1328,7 +1334,8 @@ def create(cls, name, keys=None, owner='', network=None, account_id=0, purpose=0 purpose=hdpm.purpose, scheme=scheme, parent_id=hdpm.wallet_id, sort_keys=sort_keys, witness_type=hdpm.witness_type, encoding=encoding, multisig=True, sigs_required=None, cosigner_id=wlt_cos_id, key_path=c_key_path, - db_uri=db_uri, db_cache_uri=db_cache_uri, db_password=db_password) + anti_fee_snipping=anti_fee_snipping, db_uri=db_uri, db_cache_uri=db_cache_uri, + db_password=db_password) hdpm.cosigner.append(w) wlt_cos_id += 1 # hdpm._dbwallet = hdpm.session.query(DbWallet).filter(DbWallet.id == hdpm.wallet_id) @@ -1414,6 +1421,7 @@ def __init__(self, wallet, db_uri=None, db_cache_uri=None, session=None, main_ke self.depth_public_master = self.key_path.index(hardened_keys[-1]) self.key_depth = len(self.key_path) - 1 self.last_updated = None + self.anti_fee_snipping = db_wlt.anti_fee_snipping else: raise WalletError("Wallet '%s' not found, please specify correct wallet ID or name." % wallet) @@ -3706,6 +3714,12 @@ def transaction_create(self, output_arr, input_arr=None, input_key_id=None, acco transaction.add_output(value, addr) srv = Service(network=network, providers=self.providers, cache_uri=self.db_cache_uri) + + if not locktime and self.anti_fee_snipping: + blockcount = srv.blockcount() + if blockcount: + transaction.locktime = blockcount + 1 + transaction.fee_per_kb = None if isinstance(fee, int): fee_estimate = fee @@ -3725,10 +3739,10 @@ def transaction_create(self, output_arr, input_arr=None, input_key_id=None, acco # Add inputs sequence = 0xffffffff - if 0 < transaction.locktime < 0xffffffff: - sequence = SEQUENCE_ENABLE_LOCKTIME - elif replace_by_fee: + if replace_by_fee: sequence = SEQUENCE_REPLACE_BY_FEE + elif 0 < transaction.locktime < 0xffffffff: + sequence = SEQUENCE_ENABLE_LOCKTIME amount_total_input = 0 if input_arr is None: selected_utxos = self.select_inputs(amount_total_output + fee_estimate, transaction.network.dust_amount, diff --git a/tests/bitcoinlib_encrypted.db b/tests/bitcoinlib_encrypted.db index 12d45fe411f4a47e39dc5639ecdd84e5cbc4047e..9f965e9db4e43b33f68dedc979be75e6c2f27453 100644 GIT binary patch delta 171 zcmZoTz|nAkV}i7x3IhX!5)i|H=tLc3Ruu-lvd+eottpIi?X`h2LJTDg{6YMe`MUWP z8A|wKI34(m`M7u=^6ubx!Lf)Vn1h}D2zx&Br-_XoTy52??BdeWjJ>|we|s@5Wt2?J zE6I#cOHGY0&dV$)$jnPuNXpO8Nlna~{=uD5Z~GY^#tz2q?GB7tTx_~*Y~qg6(=Yln QN^CFkV{Btw7QlD`04C}$+W-In delta 152 zcmZoTz|nAkV}i7xG6MsH5)i|H$V44uR%HggaIeOcttpIi?REKoFbFZUGw^%ypXO`k zm*o1$7s7g#>jc+)t}-rP)>>9`mKQ8@SRz?i7}}XnPi$0RVPRpeo!Dr){gX3e78h4r u0xP??v@~O{`1U#n#-)tYwYbB@v