mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 17:47:31 +00:00
Merge pull request #3762 from SomberNight/txin_type_address_segwit
change partial txn serialization format for imported addresses txins
This commit is contained in:
commit
24818c14ca
5 changed files with 272 additions and 35 deletions
|
@ -72,7 +72,7 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||||
# Take a copy; it might get updated in the main window by
|
# Take a copy; it might get updated in the main window by
|
||||||
# e.g. the FX plugin. If this happens during or after a long
|
# e.g. the FX plugin. If this happens during or after a long
|
||||||
# sign operation the signatures are lost.
|
# sign operation the signatures are lost.
|
||||||
self.tx = copy.deepcopy(tx)
|
self.tx = tx = copy.deepcopy(tx)
|
||||||
try:
|
try:
|
||||||
self.tx.deserialize()
|
self.tx.deserialize()
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
|
@ -83,6 +83,11 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||||
self.saved = False
|
self.saved = False
|
||||||
self.desc = desc
|
self.desc = desc
|
||||||
|
|
||||||
|
# if the wallet can populate the inputs with more info, do it now.
|
||||||
|
# as a result, e.g. we might learn an imported address tx is segwit,
|
||||||
|
# in which case it's ok to display txid
|
||||||
|
self.wallet.add_input_info_to_all_inputs(tx)
|
||||||
|
|
||||||
self.setMinimumWidth(950)
|
self.setMinimumWidth(950)
|
||||||
self.setWindowTitle(_("Transaction"))
|
self.setWindowTitle(_("Transaction"))
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ class CoinChooserBase(PrintError):
|
||||||
buckets[key].append(coin)
|
buckets[key].append(coin)
|
||||||
|
|
||||||
def make_Bucket(desc, coins):
|
def make_Bucket(desc, coins):
|
||||||
witness = any(Transaction.is_segwit_input(coin) for coin in coins)
|
witness = any(Transaction.is_segwit_input(coin, guess_for_address=True) for coin in coins)
|
||||||
# note that we're guessing whether the tx uses segwit based
|
# note that we're guessing whether the tx uses segwit based
|
||||||
# on this single bucket
|
# on this single bucket
|
||||||
weight = sum(Transaction.estimated_input_weight(coin, witness)
|
weight = sum(Transaction.estimated_input_weight(coin, witness)
|
||||||
|
|
|
@ -1135,14 +1135,12 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
||||||
self.assertEqual(tx.txid(), tx_copy.txid())
|
self.assertEqual(tx.txid(), tx_copy.txid())
|
||||||
|
|
||||||
# sign tx
|
# sign tx
|
||||||
#wallet_offline.can_sign(tx_copy) # FIXME
|
|
||||||
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
||||||
self.assertTrue(tx.is_complete())
|
self.assertTrue(tx.is_complete())
|
||||||
self.assertFalse(tx.is_segwit())
|
self.assertFalse(tx.is_segwit())
|
||||||
self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.txid())
|
self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.txid())
|
||||||
self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.wtxid())
|
self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.wtxid())
|
||||||
|
|
||||||
@unittest.skip("not implemented yet")
|
|
||||||
@needs_test_with_all_ecc_implementations
|
@needs_test_with_all_ecc_implementations
|
||||||
@mock.patch.object(storage.WalletStorage, '_write')
|
@mock.patch.object(storage.WalletStorage, '_write')
|
||||||
def test_sending_offline_wif_online_addr_p2wpkh_p2sh(self, mock_write):
|
def test_sending_offline_wif_online_addr_p2wpkh_p2sh(self, mock_write):
|
||||||
|
@ -1168,17 +1166,15 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
||||||
tx_copy = Transaction(tx.serialize())
|
tx_copy = Transaction(tx.serialize())
|
||||||
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
||||||
|
|
||||||
#self.assertEqual(tx.txid(), tx_copy.txid()) # FIXME
|
self.assertEqual(tx.txid(), tx_copy.txid())
|
||||||
|
|
||||||
# sign tx
|
# sign tx
|
||||||
wallet_offline.can_sign(tx_copy) # FIXME
|
|
||||||
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
||||||
self.assertTrue(tx.is_complete())
|
self.assertTrue(tx.is_complete())
|
||||||
self.assertTrue(tx.is_segwit())
|
self.assertTrue(tx.is_segwit())
|
||||||
self.assertEqual('7642816d051aa3b333b6564bb6e44fe3a5885bfe7db9860dfbc9973a5c9a6562', tx.txid())
|
self.assertEqual('7642816d051aa3b333b6564bb6e44fe3a5885bfe7db9860dfbc9973a5c9a6562', tx.txid())
|
||||||
self.assertEqual('9bb9949974954613945756c48ca5525cd5cba1b667ccb10c7a53e1ed076a1117', tx.wtxid())
|
self.assertEqual('9bb9949974954613945756c48ca5525cd5cba1b667ccb10c7a53e1ed076a1117', tx.wtxid())
|
||||||
|
|
||||||
@unittest.skip("not implemented yet")
|
|
||||||
@needs_test_with_all_ecc_implementations
|
@needs_test_with_all_ecc_implementations
|
||||||
@mock.patch.object(storage.WalletStorage, '_write')
|
@mock.patch.object(storage.WalletStorage, '_write')
|
||||||
def test_sending_offline_wif_online_addr_p2wpkh(self, mock_write):
|
def test_sending_offline_wif_online_addr_p2wpkh(self, mock_write):
|
||||||
|
@ -1204,17 +1200,15 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
||||||
tx_copy = Transaction(tx.serialize())
|
tx_copy = Transaction(tx.serialize())
|
||||||
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
||||||
|
|
||||||
#self.assertEqual(tx.txid(), tx_copy.txid()) # FIXME
|
self.assertEqual(tx.txid(), tx_copy.txid())
|
||||||
|
|
||||||
# sign tx
|
# sign tx
|
||||||
wallet_offline.can_sign(tx_copy) # FIXME
|
|
||||||
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
||||||
self.assertTrue(tx.is_complete())
|
self.assertTrue(tx.is_complete())
|
||||||
self.assertTrue(tx.is_segwit())
|
self.assertTrue(tx.is_segwit())
|
||||||
self.assertEqual('f8039bd85279f2b5698f15d47f2e338d067d09af391bd8a19467aa94d03f280c', tx.txid())
|
self.assertEqual('f8039bd85279f2b5698f15d47f2e338d067d09af391bd8a19467aa94d03f280c', tx.txid())
|
||||||
self.assertEqual('3b7cc3c3352bbb43ddc086487ac696e09f2863c3d9e8636721851b8008a83ffa', tx.wtxid())
|
self.assertEqual('3b7cc3c3352bbb43ddc086487ac696e09f2863c3d9e8636721851b8008a83ffa', tx.wtxid())
|
||||||
|
|
||||||
@unittest.skip("not implemented yet")
|
|
||||||
@needs_test_with_all_ecc_implementations
|
@needs_test_with_all_ecc_implementations
|
||||||
@mock.patch.object(storage.WalletStorage, '_write')
|
@mock.patch.object(storage.WalletStorage, '_write')
|
||||||
def test_sending_offline_xprv_online_addr_p2pkh(self, mock_write): # compressed pubkey
|
def test_sending_offline_xprv_online_addr_p2pkh(self, mock_write): # compressed pubkey
|
||||||
|
@ -1246,14 +1240,12 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
||||||
self.assertEqual(tx.txid(), tx_copy.txid())
|
self.assertEqual(tx.txid(), tx_copy.txid())
|
||||||
|
|
||||||
# sign tx
|
# sign tx
|
||||||
wallet_offline.can_sign(tx_copy) # FIXME
|
|
||||||
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
||||||
self.assertTrue(tx.is_complete())
|
self.assertTrue(tx.is_complete())
|
||||||
self.assertFalse(tx.is_segwit())
|
self.assertFalse(tx.is_segwit())
|
||||||
self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.txid())
|
self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.txid())
|
||||||
self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.wtxid())
|
self.assertEqual('e56da664631b8c666c6df38ec80c954c4ac3c4f56f040faf0070e4681e937fc4', tx.wtxid())
|
||||||
|
|
||||||
@unittest.skip("not implemented yet")
|
|
||||||
@needs_test_with_all_ecc_implementations
|
@needs_test_with_all_ecc_implementations
|
||||||
@mock.patch.object(storage.WalletStorage, '_write')
|
@mock.patch.object(storage.WalletStorage, '_write')
|
||||||
def test_sending_offline_xprv_online_addr_p2wpkh_p2sh(self, mock_write):
|
def test_sending_offline_xprv_online_addr_p2wpkh_p2sh(self, mock_write):
|
||||||
|
@ -1282,17 +1274,15 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
||||||
tx_copy = Transaction(tx.serialize())
|
tx_copy = Transaction(tx.serialize())
|
||||||
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
||||||
|
|
||||||
#self.assertEqual(tx.txid(), tx_copy.txid()) # FIXME
|
self.assertEqual(tx.txid(), tx_copy.txid())
|
||||||
|
|
||||||
# sign tx
|
# sign tx
|
||||||
wallet_offline.can_sign(tx_copy) # FIXME
|
|
||||||
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
||||||
self.assertTrue(tx.is_complete())
|
self.assertTrue(tx.is_complete())
|
||||||
self.assertTrue(tx.is_segwit())
|
self.assertTrue(tx.is_segwit())
|
||||||
self.assertEqual('7642816d051aa3b333b6564bb6e44fe3a5885bfe7db9860dfbc9973a5c9a6562', tx.txid())
|
self.assertEqual('7642816d051aa3b333b6564bb6e44fe3a5885bfe7db9860dfbc9973a5c9a6562', tx.txid())
|
||||||
self.assertEqual('9bb9949974954613945756c48ca5525cd5cba1b667ccb10c7a53e1ed076a1117', tx.wtxid())
|
self.assertEqual('9bb9949974954613945756c48ca5525cd5cba1b667ccb10c7a53e1ed076a1117', tx.wtxid())
|
||||||
|
|
||||||
@unittest.skip("not implemented yet")
|
|
||||||
@needs_test_with_all_ecc_implementations
|
@needs_test_with_all_ecc_implementations
|
||||||
@mock.patch.object(storage.WalletStorage, '_write')
|
@mock.patch.object(storage.WalletStorage, '_write')
|
||||||
def test_sending_offline_xprv_online_addr_p2wpkh(self, mock_write):
|
def test_sending_offline_xprv_online_addr_p2wpkh(self, mock_write):
|
||||||
|
@ -1321,16 +1311,189 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
||||||
tx_copy = Transaction(tx.serialize())
|
tx_copy = Transaction(tx.serialize())
|
||||||
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
||||||
|
|
||||||
#self.assertEqual(tx.txid(), tx_copy.txid()) # FIXME
|
self.assertEqual(tx.txid(), tx_copy.txid())
|
||||||
|
|
||||||
# sign tx
|
# sign tx
|
||||||
wallet_offline.can_sign(tx_copy) # FIXME
|
|
||||||
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
tx = wallet_offline.sign_transaction(tx_copy, password=None)
|
||||||
self.assertTrue(tx.is_complete())
|
self.assertTrue(tx.is_complete())
|
||||||
self.assertTrue(tx.is_segwit())
|
self.assertTrue(tx.is_segwit())
|
||||||
self.assertEqual('f8039bd85279f2b5698f15d47f2e338d067d09af391bd8a19467aa94d03f280c', tx.txid())
|
self.assertEqual('f8039bd85279f2b5698f15d47f2e338d067d09af391bd8a19467aa94d03f280c', tx.txid())
|
||||||
self.assertEqual('3b7cc3c3352bbb43ddc086487ac696e09f2863c3d9e8636721851b8008a83ffa', tx.wtxid())
|
self.assertEqual('3b7cc3c3352bbb43ddc086487ac696e09f2863c3d9e8636721851b8008a83ffa', tx.wtxid())
|
||||||
|
|
||||||
|
@needs_test_with_all_ecc_implementations
|
||||||
|
@mock.patch.object(storage.WalletStorage, '_write')
|
||||||
|
def test_sending_offline_hd_multisig_online_addr_p2sh(self, mock_write):
|
||||||
|
# 2-of-3 legacy p2sh multisig
|
||||||
|
wallet_offline1 = WalletIntegrityHelper.create_multisig_wallet(
|
||||||
|
[
|
||||||
|
keystore.from_seed('blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure', '', True),
|
||||||
|
keystore.from_xpub('tpubD6NzVbkrYhZ4YTPEgwk4zzr8wyo7pXGmbbVUnfYNtx6SgAMF5q3LN3Kch58P9hxGNsTmP7Dn49nnrmpE6upoRb1Xojg12FGLuLHkVpVtS44'),
|
||||||
|
keystore.from_xpub('tpubD6NzVbkrYhZ4XJzYkhsCbDCcZRmDAKSD7bXi9mdCni7acVt45fxbTVZyU6jRGh29ULKTjoapkfFsSJvQHitcVKbQgzgkkYsAmaovcro7Mhf')
|
||||||
|
],
|
||||||
|
'2of3', gap_limit=2
|
||||||
|
)
|
||||||
|
wallet_offline2 = WalletIntegrityHelper.create_multisig_wallet(
|
||||||
|
[
|
||||||
|
keystore.from_seed('cycle rocket west magnet parrot shuffle foot correct salt library feed song', '', True),
|
||||||
|
keystore.from_xpub('tpubD6NzVbkrYhZ4YTPEgwk4zzr8wyo7pXGmbbVUnfYNtx6SgAMF5q3LN3Kch58P9hxGNsTmP7Dn49nnrmpE6upoRb1Xojg12FGLuLHkVpVtS44'),
|
||||||
|
keystore.from_xpub('tpubD6NzVbkrYhZ4YARFMEZPckrqJkw59GZD1PXtQnw14ukvWDofR7Z1HMeSCxfYEZVvg4VdZ8zGok5VxHwdrLqew5cMdQntWc5mT7mh1CSgrnX')
|
||||||
|
],
|
||||||
|
'2of3', gap_limit=2
|
||||||
|
)
|
||||||
|
wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
|
||||||
|
wallet_online.import_address('2N4z38eTKcWTZnfugCCfRyXtXWMLnn8HDfw')
|
||||||
|
|
||||||
|
# bootstrap wallet_online
|
||||||
|
funding_tx = Transaction('010000000001016207d958dc46508d706e4cd7d3bc46c5c2b02160e2578e5fad2efafc3927050301000000171600147a4fc8cdc1c2cf7abbcd88ef6d880e59269797acfdffffff02809698000000000017a91480c2353f6a7bc3c71e99e062655b19adb3dd2e48870d0916020000000017a914703f83ef20f3a52d908475dcad00c5144164d5a2870247304402203b1a5cb48cadeee14fa6c7bbf2bc581ca63104762ec5c37c703df778884cc5b702203233fa53a2a0bfbd85617c636e415da72214e359282cce409019319d031766c50121021112c01a48cc7ea13cba70493c6bffebb3e805df10ff4611d2bf559d26e25c04bf391400')
|
||||||
|
funding_txid = funding_tx.txid()
|
||||||
|
self.assertEqual('c59913a1fa9b1ef1f6928f0db490be67eeb9d7cb05aa565ee647e859642f3532', funding_txid)
|
||||||
|
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||||
|
|
||||||
|
# create unsigned tx
|
||||||
|
outputs = [(bitcoin.TYPE_ADDRESS, '2MuCQQHJNnrXzQzuqfUCfAwAjPqpyEHbgue', 2500000)]
|
||||||
|
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
|
||||||
|
tx.set_rbf(True)
|
||||||
|
tx.locktime = 1325503
|
||||||
|
|
||||||
|
self.assertFalse(tx.is_complete())
|
||||||
|
self.assertEqual(1, len(tx.inputs()))
|
||||||
|
tx_copy = Transaction(tx.serialize())
|
||||||
|
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
||||||
|
|
||||||
|
self.assertEqual(tx.txid(), tx_copy.txid())
|
||||||
|
|
||||||
|
# sign tx - first
|
||||||
|
tx = wallet_offline1.sign_transaction(tx_copy, password=None)
|
||||||
|
self.assertFalse(tx.is_complete())
|
||||||
|
tx = Transaction(tx.serialize())
|
||||||
|
|
||||||
|
# sign tx - second
|
||||||
|
tx = wallet_offline2.sign_transaction(tx, password=None)
|
||||||
|
self.assertTrue(tx.is_complete())
|
||||||
|
tx = Transaction(tx.serialize())
|
||||||
|
|
||||||
|
self.assertEqual('010000000132352f6459e847e65e56aa05cbd7b9ee67be90b40d8f92f6f11e9bfaa11399c500000000fdfe0000483045022100cfe41e783629a2ad0b1f17cd2dbd69db05763fa7a22691131fa321ba3140d7cb02203fbda2ccc6212315464cd814d4e909b4f80a2361e3af0f9deda06478f91a0f3901483045022100b84fd63e957f2409558f63962fc91ba58334efde8b88ff53ca71da3d0fe7219702206001c6caeb30e18a7525fc72de0003e12646bf815b12fb132c1aadd6ffa1989c014c69522102afb4af9a91264e1c6dce3ebe5312801723270ac0ba8134b7b49129328fcb0f2821030b482838721a38d94847699fed8818b5c5f56500ef72f13489e365b65e5749cf2103e5db7969ae2f2576e6a061bf3bb2db16571e77ffb41e0b27170734359235cbce53aefdffffff02a02526000000000017a9141567b2578f300fa618ef0033611fd67087aff6d187585d72000000000017a91480c2353f6a7bc3c71e99e062655b19adb3dd2e4887bf391400',
|
||||||
|
str(tx))
|
||||||
|
self.assertEqual('bb4c28af28b970522c56ff0482cd98c2b78a90bec578bcede8a9e5cbec6ef5e7', tx.txid())
|
||||||
|
self.assertEqual('bb4c28af28b970522c56ff0482cd98c2b78a90bec578bcede8a9e5cbec6ef5e7', tx.wtxid())
|
||||||
|
|
||||||
|
@needs_test_with_all_ecc_implementations
|
||||||
|
@mock.patch.object(storage.WalletStorage, '_write')
|
||||||
|
def test_sending_offline_hd_multisig_online_addr_p2wsh_p2sh(self, mock_write):
|
||||||
|
# 2-of-2 p2sh-embedded segwit multisig
|
||||||
|
wallet_offline1 = WalletIntegrityHelper.create_multisig_wallet(
|
||||||
|
[
|
||||||
|
# bip39: finish seminar arrange erosion sunny coil insane together pretty lunch lunch rose, der: m/1234'/1'/0', p2wsh-p2sh multisig
|
||||||
|
keystore.from_xprv('Uprv9CvELvByqm8k2dpecJVjgLMX1z5DufEjY4fBC5YvdGF5WjGCa7GVJJ2fYni1tyuF7Hw83E6W2ZBjAhaFLZv2ri3rEsubkCd5avg4EHKoDBN'),
|
||||||
|
keystore.from_xpub('Upub5Qb8ik4Cnu8g97KLXKgVXHqY6tH8emQvqtBncjSKsyfTZuorPtTZgX7ovKKZHuuVGBVd1MTTBkWez1XXt2weN1sWBz6SfgRPQYEkNgz81QF')
|
||||||
|
],
|
||||||
|
'2of2', gap_limit=2
|
||||||
|
)
|
||||||
|
wallet_offline2 = WalletIntegrityHelper.create_multisig_wallet(
|
||||||
|
[
|
||||||
|
# bip39: square page wood spy oil story rebel give milk screen slide shuffle, der: m/1234'/1'/0', p2wsh-p2sh multisig
|
||||||
|
keystore.from_xprv('Uprv9BbnKEXJxXaNvdEsRJ9VA9toYrSeFJh5UfGBpM2iKe8Uh7UhrM9K8ioL53s8gvCoGfirHHaqpABDAE7VUNw8LNU1DMJKVoWyeNKu9XcDC19'),
|
||||||
|
keystore.from_xpub('Upub5RuakRisg8h3F7u7iL2k3UJFa1uiK7xauHamzTxYBbn4PXbM7eajr6M9Q2VCr6cVGhfhqWQqxnABvtSATuVM1xzxk4nA189jJwzaMn1QX7V')
|
||||||
|
],
|
||||||
|
'2of2', gap_limit=2
|
||||||
|
)
|
||||||
|
wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
|
||||||
|
wallet_online.import_address('2MsHQRm1pNi6VsmXYRxYMcCTdPu7Xa1RyFe')
|
||||||
|
|
||||||
|
# bootstrap wallet_online
|
||||||
|
funding_tx = Transaction('0100000000010118d494d28e5c3bf61566ca0313e22c3b561b888a317d689cc8b47b947adebd440000000017160014aec84704ea8508ddb94a3c6e53f0992d33a2a529fdffffff020f0925000000000017a91409f7aae0265787a02de22839d41e9c927768230287809698000000000017a91400698bd11c38f887f17c99846d9be96321fbf989870247304402206b906369f4075ebcfc149f7429dcfc34e11e1b7bbfc85d1185d5e9c324be0d3702203ce7fc12fd3131920fbcbb733250f05dbf7d03e18a4656232ee69d5c54dd46bd0121028a4b697a37f3f57f6e53f90db077fa9696095b277454fda839c211d640d48649c0391400')
|
||||||
|
funding_txid = funding_tx.txid()
|
||||||
|
self.assertEqual('54356de9e156b85c8516fd4d51bdb68b5513f58b4a6147483978ae254627ee3e', funding_txid)
|
||||||
|
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||||
|
|
||||||
|
# create unsigned tx
|
||||||
|
outputs = [(bitcoin.TYPE_ADDRESS, '2N8CtJRwxb2GCaiWWdSHLZHHLoZy53CCyxf', 2500000)]
|
||||||
|
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
|
||||||
|
tx.set_rbf(True)
|
||||||
|
tx.locktime = 1325504
|
||||||
|
|
||||||
|
self.assertFalse(tx.is_complete())
|
||||||
|
self.assertEqual(1, len(tx.inputs()))
|
||||||
|
tx_copy = Transaction(tx.serialize())
|
||||||
|
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
||||||
|
|
||||||
|
self.assertEqual(tx.txid(), tx_copy.txid())
|
||||||
|
|
||||||
|
# sign tx - first
|
||||||
|
tx = wallet_offline1.sign_transaction(tx_copy, password=None)
|
||||||
|
self.assertFalse(tx.is_complete())
|
||||||
|
self.assertEqual('6a58a51591142429203b62b6ddf6b799a6926882efac229998c51bee6c3573eb', tx.txid())
|
||||||
|
tx = Transaction(tx.serialize())
|
||||||
|
|
||||||
|
# sign tx - second
|
||||||
|
tx = wallet_offline2.sign_transaction(tx, password=None)
|
||||||
|
self.assertTrue(tx.is_complete())
|
||||||
|
tx = Transaction(tx.serialize())
|
||||||
|
|
||||||
|
self.assertEqual('010000000001013eee274625ae78394847614a8bf513558bb6bd514dfd16855cb856e1e96d355401000000232200206ee8d4bb1277b7dbe1d4e49b880993aa993f417a9101cb23865c7c7258732704fdffffff02a02526000000000017a914a4189ef02c95cfe36f8e880c6cb54dff0837b22687585d72000000000017a91400698bd11c38f887f17c99846d9be96321fbf98987040047304402205a9dd9eb5676196893fb08f60079a2e9f567ee39614075d8c5d9fab0f11cbbc7022039640855188ebb7bccd9e3f00b397a888766d42d00d006f1ca7457c15449285f014730440220234f6648c5741eb195f0f4cd645298a10ce02f6ef557d05df93331e21c4f58cb022058ce2af0de1c238c4a8dd3b3c7a9a0da6e381ddad7593cddfc0480f9fe5baadf0147522102975c00f6af579f9a1d283f1e5a43032deadbab2308aef30fb307c0cfe54777462102d3f47041b424a84898e315cc8ef58190f6aec79c178c12de0790890ba7166e9c52aec0391400',
|
||||||
|
str(tx))
|
||||||
|
self.assertEqual('6a58a51591142429203b62b6ddf6b799a6926882efac229998c51bee6c3573eb', tx.txid())
|
||||||
|
self.assertEqual('96d0bca1001778c54e4c3a07929fab5562c5b5a23fd1ca3aa3870cc5df2bf97d', tx.wtxid())
|
||||||
|
|
||||||
|
@needs_test_with_all_ecc_implementations
|
||||||
|
@mock.patch.object(storage.WalletStorage, '_write')
|
||||||
|
def test_sending_offline_hd_multisig_online_addr_p2wsh(self, mock_write):
|
||||||
|
# 2-of-3 p2wsh multisig
|
||||||
|
wallet_offline1 = WalletIntegrityHelper.create_multisig_wallet(
|
||||||
|
[
|
||||||
|
keystore.from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver', '', True),
|
||||||
|
keystore.from_xpub('Vpub5fcdcgEwTJmbmqAktuK8Kyq92fMf7sWkcP6oqAii2tG47dNbfkGEGUbfS9NuZaRywLkHE6EmUksrqo32ZL3ouLN1HTar6oRiHpDzKMAF1tf'),
|
||||||
|
keystore.from_xpub('Vpub5fjkKyYnvSS4wBuakWTkNvZDaBM2vQ1MeXWq368VJHNr2eT8efqhpmZ6UUkb7s2dwCXv2Vuggjdhk4vZVyiAQTwUftvff73XcUGq2NQmWra')
|
||||||
|
],
|
||||||
|
'2of3', gap_limit=2
|
||||||
|
)
|
||||||
|
wallet_offline2 = WalletIntegrityHelper.create_multisig_wallet(
|
||||||
|
[
|
||||||
|
keystore.from_seed('snow nest raise royal more walk demise rotate smooth spirit canyon gun', '', True),
|
||||||
|
keystore.from_xpub('Vpub5fjkKyYnvSS4wBuakWTkNvZDaBM2vQ1MeXWq368VJHNr2eT8efqhpmZ6UUkb7s2dwCXv2Vuggjdhk4vZVyiAQTwUftvff73XcUGq2NQmWra'),
|
||||||
|
keystore.from_xpub('Vpub5gSKXzxK7FeKQedu2q1z9oJWxqvX72AArW3HSWpEhc8othDH8xMDu28gr7gf17sp492BuJod8Tn7anjvJrKpETwqnQqX7CS8fcYyUtedEMk')
|
||||||
|
],
|
||||||
|
'2of3', gap_limit=2
|
||||||
|
)
|
||||||
|
# ^ third seed: hedgehog sunset update estate number jungle amount piano friend donate upper wool
|
||||||
|
wallet_online = WalletIntegrityHelper.create_imported_wallet(privkeys=False)
|
||||||
|
wallet_online.import_address('tb1q83p6eqxkuvq4eumcha46crpzg4nj84s9p0hnynkxg8nhvfzqcc7q4erju6')
|
||||||
|
|
||||||
|
# bootstrap wallet_online
|
||||||
|
funding_tx = Transaction('0100000000010132352f6459e847e65e56aa05cbd7b9ee67be90b40d8f92f6f11e9bfaa11399c501000000171600142e5d579693b2a7679622935df94d9f3c84909b24fdffffff0280969800000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c83717d010000000017a91441b772909ad301b41b76f4a3c5058888a7fe6f9a8702483045022100de54689f74b8efcce7fdc91e40761084686003bcd56c886ee97e75a7e803526102204dea51ae5e7d01bd56a8c336c64841f7fe02a8b101fa892e13f2d079bb14e6bf012102024e2f73d632c49f4b821ccd3b6da66b155427b1e5b1c4688cefd5a4b4bfa404c1391400')
|
||||||
|
funding_txid = funding_tx.txid()
|
||||||
|
self.assertEqual('643a7ab9083d0227dd9df314ce56b18d279e6018ff975079dfaab82cd7a66fa3', funding_txid)
|
||||||
|
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||||
|
|
||||||
|
# create unsigned tx
|
||||||
|
outputs = [(bitcoin.TYPE_ADDRESS, '2MyoZVy8T1t94yLmyKu8DP1SmbWvnxbkwRA', 2500000)]
|
||||||
|
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
|
||||||
|
tx.set_rbf(True)
|
||||||
|
tx.locktime = 1325505
|
||||||
|
|
||||||
|
self.assertFalse(tx.is_complete())
|
||||||
|
self.assertEqual(1, len(tx.inputs()))
|
||||||
|
tx_copy = Transaction(tx.serialize())
|
||||||
|
self.assertTrue(wallet_online.is_mine(wallet_online.get_txin_address(tx_copy.inputs()[0])))
|
||||||
|
|
||||||
|
self.assertEqual(tx.txid(), tx_copy.txid())
|
||||||
|
|
||||||
|
# sign tx - first
|
||||||
|
tx = wallet_offline1.sign_transaction(tx_copy, password=None)
|
||||||
|
self.assertFalse(tx.is_complete())
|
||||||
|
self.assertEqual('32e946761b4e718c1fa8d044db9e72d5831f6395eb284faf2fb5c4af0743e501', tx.txid())
|
||||||
|
tx = Transaction(tx.serialize())
|
||||||
|
|
||||||
|
# sign tx - second
|
||||||
|
tx = wallet_offline2.sign_transaction(tx, password=None)
|
||||||
|
self.assertTrue(tx.is_complete())
|
||||||
|
tx = Transaction(tx.serialize())
|
||||||
|
|
||||||
|
self.assertEqual('01000000000101a36fa6d72cb8aadf795097ff18609e278db156ce14f39ddd27023d08b97a3a640000000000fdffffff02a02526000000000017a91447ee5a659f6ffb53f7e3afc1681b6415f3c00fa187585d7200000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c04004730440220629d89626585f563202e6b38ceddc26ccd00737e0b7ee4239b9266ef9174ea2f02200b74828399a2e35ed46c9b484af4817438d5fea890606ebb201b821944db1fdc0147304402205d1a59c84c419992069e9764a7992abca6a812cc5dfd4f0d6515d4283e660ce802202597a38899f31545aaf305629bd488f36bf54e4a05fe983932cafbb3906efb8f016952210223f815ab09f6bfc8519165c5232947ae89d9d43d678fb3486f3b28382a2371fa210273c529c2c9a99592f2066cebc2172a48991af2b471cb726b9df78c6497ce984e2102aa8fc578b445a1e4257be6b978fcece92980def98dce0e1eb89e7364635ae94153aec1391400',
|
||||||
|
str(tx))
|
||||||
|
self.assertEqual('32e946761b4e718c1fa8d044db9e72d5831f6395eb284faf2fb5c4af0743e501', tx.txid())
|
||||||
|
self.assertEqual('4376fa5f1f6cb37b1f3956175d3bd4ef6882169294802b250a3c672f3ff431c1', tx.wtxid())
|
||||||
|
|
||||||
|
|
||||||
class TestWalletHistory_SimpleRandomOrder(TestCaseForTestnet):
|
class TestWalletHistory_SimpleRandomOrder(TestCaseForTestnet):
|
||||||
transactions = {
|
transactions = {
|
||||||
|
|
|
@ -385,6 +385,19 @@ def parse_scriptSig(d, _bytes):
|
||||||
d['address'] = hash160_to_p2sh(hash_160(bfh(redeem_script)))
|
d['address'] = hash160_to_p2sh(hash_160(bfh(redeem_script)))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# custom partial format for imported addresses
|
||||||
|
match = [ opcodes.OP_INVALIDOPCODE, opcodes.OP_0, opcodes.OP_PUSHDATA4 ]
|
||||||
|
if match_decoded(decoded, match):
|
||||||
|
x_pubkey = bh2u(decoded[2][1])
|
||||||
|
pubkey, address = xpubkey_to_address(x_pubkey)
|
||||||
|
d['type'] = 'address'
|
||||||
|
d['address'] = address
|
||||||
|
d['num_sig'] = 1
|
||||||
|
d['x_pubkeys'] = [x_pubkey]
|
||||||
|
d['pubkeys'] = None # get_sorted_pubkeys will populate this
|
||||||
|
d['signatures'] = [None]
|
||||||
|
return
|
||||||
|
|
||||||
print_error("parse_scriptSig: cannot find address in input script (unknown)",
|
print_error("parse_scriptSig: cannot find address in input script (unknown)",
|
||||||
bh2u(_bytes))
|
bh2u(_bytes))
|
||||||
|
|
||||||
|
@ -499,6 +512,8 @@ def parse_witness(vds, txin, full_parse: bool):
|
||||||
raise UnknownTxinType()
|
raise UnknownTxinType()
|
||||||
if txin['type'] == 'coinbase':
|
if txin['type'] == 'coinbase':
|
||||||
pass
|
pass
|
||||||
|
elif txin['type'] == 'address':
|
||||||
|
pass
|
||||||
elif txin['type'] == 'p2wsh-p2sh' or n > 2:
|
elif txin['type'] == 'p2wsh-p2sh' or n > 2:
|
||||||
witness_script_unsanitized = w[-1] # for partial multisig txn, this has x_pubkeys
|
witness_script_unsanitized = w[-1] # for partial multisig txn, this has x_pubkeys
|
||||||
try:
|
try:
|
||||||
|
@ -784,21 +799,25 @@ class Transaction:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def serialize_witness(self, txin, estimate_size=False):
|
def serialize_witness(self, txin, estimate_size=False):
|
||||||
if not self.is_segwit_input(txin):
|
_type = txin['type']
|
||||||
|
if not self.is_segwit_input(txin) and not self.is_input_value_needed(txin):
|
||||||
return '00'
|
return '00'
|
||||||
if txin['type'] == 'coinbase':
|
if _type == 'coinbase':
|
||||||
return txin['witness']
|
return txin['witness']
|
||||||
|
|
||||||
witness = txin.get('witness', None)
|
witness = txin.get('witness', None)
|
||||||
if witness is None:
|
if witness is None or estimate_size:
|
||||||
|
if _type == 'address' and estimate_size:
|
||||||
|
_type = self.guess_txintype_from_address(txin['address'])
|
||||||
pubkeys, sig_list = self.get_siglist(txin, estimate_size)
|
pubkeys, sig_list = self.get_siglist(txin, estimate_size)
|
||||||
if txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']:
|
if _type in ['p2wpkh', 'p2wpkh-p2sh']:
|
||||||
witness = construct_witness([sig_list[0], pubkeys[0]])
|
witness = construct_witness([sig_list[0], pubkeys[0]])
|
||||||
elif txin['type'] in ['p2wsh', 'p2wsh-p2sh']:
|
elif _type in ['p2wsh', 'p2wsh-p2sh']:
|
||||||
witness_script = multisig_script(pubkeys, txin['num_sig'])
|
witness_script = multisig_script(pubkeys, txin['num_sig'])
|
||||||
witness = construct_witness([0] + sig_list + [witness_script])
|
witness = construct_witness([0] + sig_list + [witness_script])
|
||||||
else:
|
else:
|
||||||
raise Exception('wrong txin type:', txin['type'])
|
witness = txin.get('witness', '00')
|
||||||
|
|
||||||
if self.is_txin_complete(txin) or estimate_size:
|
if self.is_txin_complete(txin) or estimate_size:
|
||||||
partial_format_witness_prefix = ''
|
partial_format_witness_prefix = ''
|
||||||
else:
|
else:
|
||||||
|
@ -808,14 +827,41 @@ class Transaction:
|
||||||
return partial_format_witness_prefix + witness
|
return partial_format_witness_prefix + witness
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_segwit_input(cls, txin):
|
def is_segwit_input(cls, txin, guess_for_address=False):
|
||||||
|
_type = txin['type']
|
||||||
|
if _type == 'address' and guess_for_address:
|
||||||
|
_type = cls.guess_txintype_from_address(txin['address'])
|
||||||
has_nonzero_witness = txin.get('witness', '00') not in ('00', None)
|
has_nonzero_witness = txin.get('witness', '00') not in ('00', None)
|
||||||
return cls.is_segwit_inputtype(txin['type']) or has_nonzero_witness
|
return cls.is_segwit_inputtype(_type) or has_nonzero_witness
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_segwit_inputtype(cls, txin_type):
|
def is_segwit_inputtype(cls, txin_type):
|
||||||
return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')
|
return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_input_value_needed(cls, txin):
|
||||||
|
return cls.is_segwit_input(txin) or txin['type'] == 'address'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def guess_txintype_from_address(cls, addr):
|
||||||
|
# It's not possible to tell the script type in general
|
||||||
|
# just from an address.
|
||||||
|
# - "1" addresses are of course p2pkh
|
||||||
|
# - "3" addresses are p2sh but we don't know the redeem script..
|
||||||
|
# - "bc1" addresses (if they are 42-long) are p2wpkh
|
||||||
|
# - "bc1" addresses that are 62-long are p2wsh but we don't know the script..
|
||||||
|
# If we don't know the script, we _guess_ it is pubkeyhash.
|
||||||
|
# As this method is used e.g. for tx size estimation,
|
||||||
|
# the estimation will not be precise.
|
||||||
|
witver, witprog = segwit_addr.decode(constants.net.SEGWIT_HRP, addr)
|
||||||
|
if witprog is not None:
|
||||||
|
return 'p2wpkh'
|
||||||
|
addrtype, hash_160 = b58_address_to_hash160(addr)
|
||||||
|
if addrtype == constants.net.ADDRTYPE_P2PKH:
|
||||||
|
return 'p2pkh'
|
||||||
|
elif addrtype == constants.net.ADDRTYPE_P2SH:
|
||||||
|
return 'p2wpkh-p2sh'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def input_script(self, txin, estimate_size=False):
|
def input_script(self, txin, estimate_size=False):
|
||||||
_type = txin['type']
|
_type = txin['type']
|
||||||
|
@ -832,6 +878,8 @@ class Transaction:
|
||||||
|
|
||||||
pubkeys, sig_list = self.get_siglist(txin, estimate_size)
|
pubkeys, sig_list = self.get_siglist(txin, estimate_size)
|
||||||
script = ''.join(push_script(x) for x in sig_list)
|
script = ''.join(push_script(x) for x in sig_list)
|
||||||
|
if _type == 'address' and estimate_size:
|
||||||
|
_type = self.guess_txintype_from_address(txin['address'])
|
||||||
if _type == 'p2pk':
|
if _type == 'p2pk':
|
||||||
pass
|
pass
|
||||||
elif _type == 'p2sh':
|
elif _type == 'p2sh':
|
||||||
|
@ -855,7 +903,7 @@ class Transaction:
|
||||||
scriptSig = bitcoin.p2wsh_nested_script(witness_script)
|
scriptSig = bitcoin.p2wsh_nested_script(witness_script)
|
||||||
return push_script(scriptSig)
|
return push_script(scriptSig)
|
||||||
elif _type == 'address':
|
elif _type == 'address':
|
||||||
script += push_script(pubkeys[0])
|
return 'ff00' + push_script(pubkeys[0]) # fd extended pubkey
|
||||||
elif _type == 'unknown':
|
elif _type == 'unknown':
|
||||||
return txin['scriptSig']
|
return txin['scriptSig']
|
||||||
return script
|
return script
|
||||||
|
@ -956,10 +1004,10 @@ class Transaction:
|
||||||
preimage = nVersion + txins + txouts + nLocktime + nHashType
|
preimage = nVersion + txins + txouts + nLocktime + nHashType
|
||||||
return preimage
|
return preimage
|
||||||
|
|
||||||
def is_segwit(self):
|
def is_segwit(self, guess_for_address=False):
|
||||||
if not self.is_partial_originally:
|
if not self.is_partial_originally:
|
||||||
return self._segwit_ser
|
return self._segwit_ser
|
||||||
return any(self.is_segwit_input(x) for x in self.inputs())
|
return any(self.is_segwit_input(x, guess_for_address=guess_for_address) for x in self.inputs())
|
||||||
|
|
||||||
def serialize(self, estimate_size=False, witness=True):
|
def serialize(self, estimate_size=False, witness=True):
|
||||||
network_ser = self.serialize_to_network(estimate_size, witness)
|
network_ser = self.serialize_to_network(estimate_size, witness)
|
||||||
|
@ -978,7 +1026,11 @@ class Transaction:
|
||||||
outputs = self.outputs()
|
outputs = self.outputs()
|
||||||
txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs)
|
txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs)
|
||||||
txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)
|
txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)
|
||||||
if witness and self.is_segwit():
|
use_segwit_ser_for_estimate_size = estimate_size and self.is_segwit(guess_for_address=True)
|
||||||
|
use_segwit_ser_for_actual_use = not estimate_size and \
|
||||||
|
(self.is_segwit() or any(txin['type'] == 'address' for txin in inputs))
|
||||||
|
use_segwit_ser = use_segwit_ser_for_estimate_size or use_segwit_ser_for_actual_use
|
||||||
|
if witness and use_segwit_ser:
|
||||||
marker = '00'
|
marker = '00'
|
||||||
flag = '01'
|
flag = '01'
|
||||||
witness = ''.join(self.serialize_witness(x, estimate_size) for x in inputs)
|
witness = ''.join(self.serialize_witness(x, estimate_size) for x in inputs)
|
||||||
|
@ -1038,8 +1090,7 @@ class Transaction:
|
||||||
script = cls.input_script(txin, True)
|
script = cls.input_script(txin, True)
|
||||||
input_size = len(cls.serialize_input(txin, script)) // 2
|
input_size = len(cls.serialize_input(txin, script)) // 2
|
||||||
|
|
||||||
if cls.is_segwit_input(txin):
|
if cls.is_segwit_input(txin, guess_for_address=True):
|
||||||
assert is_segwit_tx
|
|
||||||
witness_size = len(cls.serialize_witness(txin, True)) // 2
|
witness_size = len(cls.serialize_witness(txin, True)) // 2
|
||||||
else:
|
else:
|
||||||
witness_size = 1 if is_segwit_tx else 0
|
witness_size = 1 if is_segwit_tx else 0
|
||||||
|
@ -1063,10 +1114,10 @@ class Transaction:
|
||||||
|
|
||||||
def estimated_witness_size(self):
|
def estimated_witness_size(self):
|
||||||
"""Return an estimate of witness size in bytes."""
|
"""Return an estimate of witness size in bytes."""
|
||||||
if not self.is_segwit():
|
estimate = not self.is_complete()
|
||||||
|
if not self.is_segwit(guess_for_address=estimate):
|
||||||
return 0
|
return 0
|
||||||
inputs = self.inputs()
|
inputs = self.inputs()
|
||||||
estimate = not self.is_complete()
|
|
||||||
witness = ''.join(self.serialize_witness(x, estimate) for x in inputs)
|
witness = ''.join(self.serialize_witness(x, estimate) for x in inputs)
|
||||||
witness_size = len(witness) // 2 + 2 # include marker and flag
|
witness_size = len(witness) // 2 + 2 # include marker and flag
|
||||||
return witness_size
|
return witness_size
|
||||||
|
|
|
@ -1443,21 +1443,32 @@ class Abstract_Wallet(PrintError):
|
||||||
# note: no need to call tx.BIP_LI01_sort() here - single input/output
|
# note: no need to call tx.BIP_LI01_sort() here - single input/output
|
||||||
return Transaction.from_io(inputs, outputs, locktime=locktime)
|
return Transaction.from_io(inputs, outputs, locktime=locktime)
|
||||||
|
|
||||||
|
def add_input_sig_info(self, txin, address):
|
||||||
|
raise NotImplementedError() # implemented by subclasses
|
||||||
|
|
||||||
def add_input_info(self, txin):
|
def add_input_info(self, txin):
|
||||||
address = txin['address']
|
address = txin['address']
|
||||||
if self.is_mine(address):
|
if self.is_mine(address):
|
||||||
txin['type'] = self.get_txin_type(address)
|
txin['type'] = self.get_txin_type(address)
|
||||||
# segwit needs value to sign
|
# segwit needs value to sign
|
||||||
if txin.get('value') is None and Transaction.is_segwit_input(txin):
|
if txin.get('value') is None and Transaction.is_input_value_needed(txin):
|
||||||
received, spent = self.get_addr_io(address)
|
received, spent = self.get_addr_io(address)
|
||||||
item = received.get(txin['prevout_hash']+':%d'%txin['prevout_n'])
|
item = received.get(txin['prevout_hash']+':%d'%txin['prevout_n'])
|
||||||
tx_height, value, is_cb = item
|
tx_height, value, is_cb = item
|
||||||
txin['value'] = value
|
txin['value'] = value
|
||||||
self.add_input_sig_info(txin, address)
|
self.add_input_sig_info(txin, address)
|
||||||
|
|
||||||
|
def add_input_info_to_all_inputs(self, tx):
|
||||||
|
if tx.is_complete():
|
||||||
|
return
|
||||||
|
for txin in tx.inputs():
|
||||||
|
self.add_input_info(txin)
|
||||||
|
|
||||||
def can_sign(self, tx):
|
def can_sign(self, tx):
|
||||||
if tx.is_complete():
|
if tx.is_complete():
|
||||||
return False
|
return False
|
||||||
|
# add info to inputs if we can; otherwise we might return a false negative:
|
||||||
|
self.add_input_info_to_all_inputs(tx) # though note that this is a side-effect
|
||||||
for k in self.get_keystores():
|
for k in self.get_keystores():
|
||||||
if k.can_sign(tx):
|
if k.can_sign(tx):
|
||||||
return True
|
return True
|
||||||
|
@ -1500,6 +1511,7 @@ class Abstract_Wallet(PrintError):
|
||||||
def sign_transaction(self, tx, password):
|
def sign_transaction(self, tx, password):
|
||||||
if self.is_watching_only():
|
if self.is_watching_only():
|
||||||
return
|
return
|
||||||
|
self.add_input_info_to_all_inputs(tx)
|
||||||
# hardware wallets require extra info
|
# hardware wallets require extra info
|
||||||
if any([(isinstance(k, Hardware_KeyStore) and k.can_sign(tx)) for k in self.get_keystores()]):
|
if any([(isinstance(k, Hardware_KeyStore) and k.can_sign(tx)) for k in self.get_keystores()]):
|
||||||
self.add_hw_info(tx)
|
self.add_hw_info(tx)
|
||||||
|
@ -2318,7 +2330,13 @@ class Multisig_Wallet(Deterministic_Wallet):
|
||||||
# they are sorted in transaction.get_sorted_pubkeys
|
# they are sorted in transaction.get_sorted_pubkeys
|
||||||
# pubkeys is set to None to signal that x_pubkeys are unsorted
|
# pubkeys is set to None to signal that x_pubkeys are unsorted
|
||||||
derivation = self.get_address_index(address)
|
derivation = self.get_address_index(address)
|
||||||
txin['x_pubkeys'] = [k.get_xpubkey(*derivation) for k in self.get_keystores()]
|
x_pubkeys_expected = [k.get_xpubkey(*derivation) for k in self.get_keystores()]
|
||||||
|
x_pubkeys_actual = txin.get('x_pubkeys')
|
||||||
|
# if 'x_pubkeys' is already set correctly (ignoring order, as above), leave it.
|
||||||
|
# otherwise we might delete signatures
|
||||||
|
if x_pubkeys_actual and set(x_pubkeys_actual) == set(x_pubkeys_expected):
|
||||||
|
return
|
||||||
|
txin['x_pubkeys'] = x_pubkeys_expected
|
||||||
txin['pubkeys'] = None
|
txin['pubkeys'] = None
|
||||||
# we need n place holders
|
# we need n place holders
|
||||||
txin['signatures'] = [None] * self.n
|
txin['signatures'] = [None] * self.n
|
||||||
|
|
Loading…
Add table
Reference in a new issue