From a05bf88543a87ebedb4d5e9798d70e3dcea59be7 Mon Sep 17 00:00:00 2001 From: Lennart Jongeneel Date: Wed, 13 Mar 2024 20:36:34 +0100 Subject: [PATCH] Add anti-fee-sniping option to commandline wallet --- bitcoinlib/db.py | 2 +- bitcoinlib/tools/clw.py | 7 +++++-- bitcoinlib/wallets.py | 22 +++++++++++----------- tests/test_tools.py | 14 +++++++++----- tests/test_wallets.py | 9 ++++----- 5 files changed, 30 insertions(+), 24 deletions(-) diff --git a/bitcoinlib/db.py b/bitcoinlib/db.py index ba8486bb..01a1c9cf 100644 --- a/bitcoinlib/db.py +++ b/bitcoinlib/db.py @@ -248,7 +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") + anti_fee_sniping = Column(Boolean, default=True, doc="Set default locktime in transactions to avoid fee-sniping") default_account_id = Column(Integer, doc="ID of default account for this wallet if multiple accounts are used") __table_args__ = ( diff --git a/bitcoinlib/tools/clw.py b/bitcoinlib/tools/clw.py index e3df4e75..27bf3a15 100644 --- a/bitcoinlib/tools/clw.py +++ b/bitcoinlib/tools/clw.py @@ -89,7 +89,9 @@ def parse_args(): parser_new.add_argument('--yes', '-y', action='store_true', default=False, help='Non-interactive mode, does not prompt for confirmation') parser_new.add_argument('--quiet', '-q', action='store_true', - help='Quit mode, no output writen to console.') + help='Quiet mode, no output writen to console.') + parser_new.add_argument('--disable-anti-fee-sniping', action='store_true', default=False, + help='Disable anti-fee-sniping, and set locktime in all transaction to zero.') group_wallet = parser.add_argument_group("Wallet Actions") group_wallet.add_argument('--wallet-remove', action='store_true', @@ -188,7 +190,8 @@ def create_wallet(wallet_name, args, db_uri, output_to): passphrase = get_passphrase(args.passphrase_strength, args.yes, args.quiet) key_list.append(HDKey.from_passphrase(passphrase, network=args.network)) return Wallet.create(wallet_name, key_list, sigs_required=sigs_required, network=args.network, - cosigner_id=args.cosigner_id, db_uri=db_uri, witness_type=args.witness_type) + cosigner_id=args.cosigner_id, db_uri=db_uri, witness_type=args.witness_type, + anti_fee_sniping=not(args.disable_anti_fee_sniping)) elif args.create_from_key: from bitcoinlib.keys import get_key_format import_key = args.create_from_key diff --git a/bitcoinlib/wallets.py b/bitcoinlib/wallets.py index 25c935df..f7a8ed5a 100644 --- a/bitcoinlib/wallets.py +++ b/bitcoinlib/wallets.py @@ -109,7 +109,7 @@ def wallet_exists(wallet, db_uri=None, db_password=None): def wallet_create_or_open( name, keys='', owner='', network=None, account_id=0, purpose=None, scheme='bip32', sort_keys=True, password='', witness_type=None, encoding=None, multisig=None, sigs_required=None, cosigner_id=None, - key_path=None, anti_fee_snipping=True, db_uri=None, db_cache_uri=None, db_password=None): + key_path=None, anti_fee_sniping=True, db_uri=None, db_cache_uri=None, db_password=None): """ Create a wallet with specified options if it doesn't exist, otherwise just open @@ -124,7 +124,7 @@ def wallet_create_or_open( else: return Wallet.create(name, keys, owner, network, account_id, purpose, scheme, sort_keys, password, witness_type, encoding, multisig, sigs_required, cosigner_id, - key_path, anti_fee_snipping, db_uri=db_uri, db_cache_uri=db_cache_uri, + key_path, anti_fee_sniping, db_uri=db_uri, db_cache_uri=db_cache_uri, db_password=db_password) @@ -1045,7 +1045,7 @@ 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, - anti_fee_snipping, db_uri, db_cache_uri, db_password): + anti_fee_sniping, db_uri, db_cache_uri, db_password): db = Db(db_uri, db_password) session = db.session @@ -1085,7 +1085,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, anti_fee_snipping=anti_fee_snipping) + key_path=key_path, anti_fee_sniping=anti_fee_sniping) session.add(new_wallet) session.commit() new_wallet_id = new_wallet.id @@ -1124,7 +1124,7 @@ 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, anti_fee_snipping=True, db_uri=None, db_cache_uri=None, + cosigner_id=None, key_path=None, anti_fee_sniping=True, db_uri=None, db_cache_uri=None, db_password=None): """ Create Wallet and insert in database. Generate masterkey or import key when specified. @@ -1196,8 +1196,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, which will make the network more secure. You could disable it to avoid transaction fingerprinting. - :type anti_fee_snipping: boolean + :param anti_fee_sniping: Set default locktime in transactions as current block height + 1 to avoid fee-sniping. Default is True, which will make the network more secure. You could disable it to avoid transaction fingerprinting. + :type anti_fee_sniping: 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) @@ -1316,7 +1316,7 @@ 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, - anti_fee_snipping=anti_fee_snipping, key_path=main_key_path, db_uri=db_uri, + anti_fee_sniping=anti_fee_sniping, key_path=main_key_path, db_uri=db_uri, db_cache_uri=db_cache_uri, db_password=db_password) if multisig: @@ -1335,7 +1335,7 @@ 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, - anti_fee_snipping=anti_fee_snipping, db_uri=db_uri, db_cache_uri=db_cache_uri, + anti_fee_sniping=anti_fee_sniping, db_uri=db_uri, db_cache_uri=db_cache_uri, db_password=db_password) hdpm.cosigner.append(w) wlt_cos_id += 1 @@ -1422,7 +1422,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 + self.anti_fee_sniping = db_wlt.anti_fee_sniping else: raise WalletError("Wallet '%s' not found, please specify correct wallet ID or name." % wallet) @@ -3716,7 +3716,7 @@ def transaction_create(self, output_arr, input_arr=None, input_key_id=None, acco srv = Service(network=network, providers=self.providers, cache_uri=self.db_cache_uri) - if not locktime and self.anti_fee_snipping: + if not locktime and self.anti_fee_sniping: blockcount = srv.blockcount() if blockcount: transaction.locktime = blockcount + 1 diff --git a/tests/test_tools.py b/tests/test_tools.py index 03be68b9..b37998a0 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -87,6 +87,7 @@ def test_tools_clw_create_multisig_wallet(self): ] cmd_wlt_create = "%s %s new -w testms -m 2 2 %s -r -n testnet -d %s -o 0" % \ (self.python_executable, self.clw_executable, ' '.join(key_list), self.database_uri) + print(cmd_wlt_create) cmd_wlt_delete = "%s %s -w testms --wallet-remove -d %s" % \ (self.python_executable, self.clw_executable, self.database_uri) output_wlt_create = "2NBrLTapyFqU4Wo29xG4QeEt8kn38KVWRR" @@ -276,11 +277,14 @@ def test_tools_wallet_multisig_cosigners(self): 'ZYXRnhWiS3jjHqgeZ') pub_key3 = ('BC11mYrL5yBtMgaYxHEUg3anvLX3gcLi8hbtwbjymReCgGiP6hYifVMi96M3ejtvZpZbDvetBfbzgRxmu22ZkqP2i7yhFge' 'mSkHp7BRhoDubrQvs') - cmd_wlt_create1 = "%s %s new -w wlt_multisig_2_3_A -m 2 3 %s %s %s -d %s -n bitcoinlib_test -q" % \ - (self.python_executable, self.clw_executable, pk1, pub_key2, pub_key3, self.database_uri) + cmd_wlt_create1 = ("%s %s new -w wlt_multisig_2_3_A -m 2 3 %s %s %s -d %s -n bitcoinlib_test -q " + "--disable-anti-fee-sniping") % \ + (self.python_executable, self.clw_executable, pk1, pub_key2, pub_key3, self.database_uri) Popen(cmd_wlt_create1, stdin=PIPE, stdout=PIPE, shell=True).communicate() - cmd_wlt_create2 = "%s %s new -w wlt_multisig_2_3_B -m 2 3 %s %s %s -d %s -n bitcoinlib_test -q" % \ - (self.python_executable, self.clw_executable, pub_key1, pub_key2, pk3, self.database_uri) + cmd_wlt_create2 = ("%s %s new -w wlt_multisig_2_3_B -m 2 3 %s %s %s -d %s -n bitcoinlib_test -q " + "--disable-anti-fee-sniping") % \ + (self.python_executable, self.clw_executable, pub_key1, pub_key2, pk3, self.database_uri) + print(cmd_wlt_create2) Popen(cmd_wlt_create2, stdin=PIPE, stdout=PIPE, shell=True).communicate() cmd_wlt_receive1 = "%s %s -w wlt_multisig_2_3_A -d %s -r -o 1 -q" % \ @@ -313,7 +317,7 @@ def test_tools_wallet_multisig_cosigners(self): self.assertIn("'verified': True,", response) filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'import_test.tx') - sign_import_tx_file = "%s %s -w wlt_multisig_2_3_B -d %s -o 1 --import-tx-file %s" % \ + sign_import_tx_file = "%s %s -w wlt_multisig_2_3_B -d %s -o 1 --import-tx-file %s" % \ (self.python_executable, self.clw_executable, self.database_uri, filename) output = Popen(sign_import_tx_file, stdin=PIPE, stdout=PIPE, shell=True).communicate() response2 = normalize_string(output[0]) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 715b1c3b..40b8622c 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -2266,20 +2266,19 @@ def test_wallet_transaction_replace_by_fee(self): self.assertTrue(t2.replace_by_fee) self.assertEqual(t2.inputs[0].sequence, SEQUENCE_REPLACE_BY_FEE) - def test_wallet_anti_fee_snipping(self): - w = wallet_create_or_open('antifeesnippingtestwallet', network='testnet', anti_fee_snipping=True, - db_uri=self.database_uri) + def test_wallet_anti_fee_sniping(self): + w = wallet_create_or_open('antifeesnipingtestwallet', network='testnet', db_uri=self.database_uri) w.utxo_add(w.get_key().address, 1234567, os.urandom(32).hex(), 1) t = w.send_to('tb1qrjtz22q59e76mhumy0p586cqukatw5vcd0xvvz', 123456) block_height = Service(network='testnet').blockcount() self.assertEqual(t.locktime, block_height+1) - w2 = wallet_create_or_open('antifeesnippingtestwallet2', network='testnet', anti_fee_snipping=True) + w2 = wallet_create_or_open('antifeesnipingtestwallet2', network='testnet', anti_fee_sniping=True) w2.utxo_add(w2.get_key().address, 1234567, os.urandom(32).hex(), 1) t = w2.send_to('tb1qrjtz22q59e76mhumy0p586cqukatw5vcd0xvvz', 123456, locktime=1901070183) self.assertEqual(t.locktime, 1901070183) - w3 = wallet_create_or_open('antifeesnippingtestwallet3', network='testnet', anti_fee_snipping=False) + w3 = wallet_create_or_open('antifeesnipingtestwallet3', network='testnet', anti_fee_sniping=False) w3.utxo_add(w3.get_key().address, 1234567, os.urandom(32).hex(), 1) t = w3.send_to('tb1qrjtz22q59e76mhumy0p586cqukatw5vcd0xvvz', 123456) self.assertEqual(t.locktime, 0)