Browse Source

wallet.get_tx_status: fix incorrect feerate for partial txs

The history tab would show an incorrect feerate for partial/unsigned (local) txs,
if they had any p2sh/p2wsh txins. We would just guess the script is p2wpkh, and
use that for the size calc. Now with calling add_info_from_wallet, the correct
size is used to calculate the feerate.

(The gui tx dialogs call add_info_from_wallet independently, so the size/feerate
shown there were already correct.)
master
SomberNight 1 year ago
parent
commit
16d2f0f9d4
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 4
      electrum/wallet.py
  2. 68
      tests/test_wallet_vertical.py

4
electrum/wallet.py

@ -1645,7 +1645,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
with self.lock: with self.lock:
return copy.copy(self._labels) return copy.copy(self._labels)
def get_tx_status(self, tx_hash, tx_mined_info: TxMinedInfo): def get_tx_status(self, tx_hash: str, tx_mined_info: TxMinedInfo):
extra = [] extra = []
height = tx_mined_info.height height = tx_mined_info.height
conf = tx_mined_info.conf conf = tx_mined_info.conf
@ -1658,6 +1658,8 @@ class Abstract_Wallet(ABC, Logger, EventListener):
tx = self.db.get_transaction(tx_hash) tx = self.db.get_transaction(tx_hash)
if not tx: if not tx:
return 2, _("unknown") return 2, _("unknown")
if not tx.is_complete():
tx.add_info_from_wallet(self) # needed for estimated_size(), for txin size calc
fee = self.adb.get_tx_fee(tx_hash) fee = self.adb.get_tx_fee(tx_hash)
if fee is not None: if fee is not None:
size = tx.estimated_size() size = tx.estimated_size()

68
tests/test_wallet_vertical.py

@ -10,12 +10,12 @@ from electrum import storage, bitcoin, keystore, bip32, slip39, wallet
from electrum import Transaction from electrum import Transaction
from electrum import SimpleConfig from electrum import SimpleConfig
from electrum import util from electrum import util
from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_LOCAL
from electrum.wallet import (sweep, Multisig_Wallet, Standard_Wallet, Imported_Wallet, from electrum.wallet import (sweep, Multisig_Wallet, Standard_Wallet, Imported_Wallet,
restore_wallet_from_text, Abstract_Wallet, CannotBumpFee, BumpFeeStrategy, restore_wallet_from_text, Abstract_Wallet, CannotBumpFee, BumpFeeStrategy,
TransactionPotentiallyDangerousException, TransactionDangerousException, TransactionPotentiallyDangerousException, TransactionDangerousException,
TxSighashRiskLevel) TxSighashRiskLevel)
from electrum.util import bfh, NotEnoughFunds, UnrelatedTransactionException, UserFacingException from electrum.util import bfh, NotEnoughFunds, UnrelatedTransactionException, UserFacingException, TxMinedInfo
from electrum.transaction import Transaction, PartialTxOutput, tx_from_any, Sighash from electrum.transaction import Transaction, PartialTxOutput, tx_from_any, Sighash
from electrum.mnemonic import calc_seed_type from electrum.mnemonic import calc_seed_type
from electrum.network import Network from electrum.network import Network
@ -4183,6 +4183,70 @@ class TestWalletHistory_DoubleSpend(ElectrumTestCase):
self.assertEqual(999890, sum(w.get_balance())) self.assertEqual(999890, sum(w.get_balance()))
class TestWalletHistory_HelperFns(ElectrumTestCase):
TESTNET = True
def setUp(self):
super().setUp()
self.config = SimpleConfig({'electrum_path': self.electrum_path})
@mock.patch.object(wallet.Abstract_Wallet, 'save_db')
async def test_get_tx_status_feerate_for_local_2of3_multisig_partial_tx(self, mock_save_db):
wallet1 = WalletIntegrityHelper.create_multisig_wallet(
[
keystore.from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver', passphrase='', for_multisig=True),
keystore.from_xpub('Vpub5fcdcgEwTJmbmqAktuK8Kyq92fMf7sWkcP6oqAii2tG47dNbfkGEGUbfS9NuZaRywLkHE6EmUksrqo32ZL3ouLN1HTar6oRiHpDzKMAF1tf'),
keystore.from_xpub('Vpub5fjkKyYnvSS4wBuakWTkNvZDaBM2vQ1MeXWq368VJHNr2eT8efqhpmZ6UUkb7s2dwCXv2Vuggjdhk4vZVyiAQTwUftvff73XcUGq2NQmWra')
],
'2of3', gap_limit=2,
config=self.config,
)
# bootstrap wallet1
funding_tx = Transaction('01000000000101a41aae475d026c9255200082c7fad26dc47771275b0afba238dccda98a597bd20000000000fdffffff02400d0300000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c9dcd410000000000160014824626055515f3ed1d2cfc9152d2e70685c71e8f02483045022100b9f39fad57d07ce1e18251424034f21f10f20e59931041b5167ae343ce973cf602200fefb727fa0ffd25b353f1bcdae2395898fe407b692c62f5885afbf52fa06f5701210301a28f68511ace43114b674371257bb599fd2c686c4b19544870b1799c954b40e9c11300')
wallet1.adb.receive_tx_callback(funding_tx, TX_HEIGHT_UNCONFIRMED)
# wallet1 -> wallet2
outputs = [PartialTxOutput.from_address_and_value("2MuUcGmQ2mLN3vjTuqDSgZpk4LPKDsuPmhN", 165000)]
tx = wallet1.create_transaction(outputs=outputs, password=None, fee=5000, tx_version=1, rbf=False, sign=False)
self.assertEqual(
"wsh(sortedmulti(2,[b2e35a7d/1h]tpubD9aPYLPPYw8MxU3cD57LwpV5v7GomHxdv62MSbPcRkp47zwXx69ACUFsKrj8xzuzRrij9FWVhfvkvNqtqsr8ZtefkDsGZ9GLuHzoS6bXyk1/0/0,[53b77ddb/1h]tpubD8spLJysN7v7V1KHvkZ7AwjnXShKafopi7Vu3Ahs2S46FxBPTode8DgGxDo55k4pJvETGScZFwnM5f2Y31EUjteJdhxR73sjr9ieydgah2U/0/0,[43067d63/1h]tpubD8khd1g1tzFeKeaU59QV811hyvhwn9KDfy5sqFJ5m2wJLw6rUt4AZviqutRPXTUAK4SpU2we3y2WBP916Ma8Em4qFGcbYkFvXVfpGYV3oZR/0/0))",
tx.inputs()[0].script_descriptor.to_string_no_checksum())
partial_tx = tx.serialize_as_bytes().hex()
self.assertEqual("70736274ff01007e0100000001213e1012a461e056752fab5a6414a2fb63f950cd21a50ac5e2b82d339d6cbdd20000000000feffffff023075000000000000220020cc5e4cc05a76d0648cd0742768556317e9f8cc729aed077134287909035dba88888402000000000017a914187842cea9c15989a51ce7ca889a08b824bf874387000000000001012b400d0300000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c0100eb01000000000101a41aae475d026c9255200082c7fad26dc47771275b0afba238dccda98a597bd20000000000fdffffff02400d0300000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c9dcd410000000000160014824626055515f3ed1d2cfc9152d2e70685c71e8f02483045022100b9f39fad57d07ce1e18251424034f21f10f20e59931041b5167ae343ce973cf602200fefb727fa0ffd25b353f1bcdae2395898fe407b692c62f5885afbf52fa06f5701210301a28f68511ace43114b674371257bb599fd2c686c4b19544870b1799c954b40e9c1130001056952210223f815ab09f6bfc8519165c5232947ae89d9d43d678fb3486f3b28382a2371fa210273c529c2c9a99592f2066cebc2172a48991af2b471cb726b9df78c6497ce984e2102aa8fc578b445a1e4257be6b978fcece92980def98dce0e1eb89e7364635ae94153ae22060223f815ab09f6bfc8519165c5232947ae89d9d43d678fb3486f3b28382a2371fa10b2e35a7d01000080000000000000000022060273c529c2c9a99592f2066cebc2172a48991af2b471cb726b9df78c6497ce984e1053b77ddb010000800000000000000000220602aa8fc578b445a1e4257be6b978fcece92980def98dce0e1eb89e7364635ae9411043067d6301000080000000000000000000010169522102174696a58a8dcd6c6455bd25e0749e9a6fc7d84ee09e192ab37b0d0b18c2de1a2102c807a19ca6783261f8c198ffcc437622e7ecba8d6c5692f3a5e7f1e45af53fd52102eee40c7e24d89639182db32f5e9188613e4bc212da2ee9b4ccc85d9b82e1a98053ae220202174696a58a8dcd6c6455bd25e0749e9a6fc7d84ee09e192ab37b0d0b18c2de1a1053b77ddb010000800100000000000000220202c807a19ca6783261f8c198ffcc437622e7ecba8d6c5692f3a5e7f1e45af53fd51043067d63010000800100000000000000220202eee40c7e24d89639182db32f5e9188613e4bc212da2ee9b4ccc85d9b82e1a98010b2e35a7d0100008001000000000000000000",
partial_tx)
tx = tx_from_any(partial_tx) # simulates moving partial txn between cosigners
self.assertFalse(tx.is_complete())
wallet1.adb.add_transaction(tx)
# let's see if the calculated feerate correct:
self.assertEqual((3, 'Local [26.3 sat/vB]'),
wallet1.get_tx_status(tx.txid(), TxMinedInfo(height=TX_HEIGHT_LOCAL, conf=0)))
@mock.patch.object(wallet.Abstract_Wallet, 'save_db')
async def test_get_tx_status_feerate_for_local_2of3_multisig_signed_tx(self, mock_save_db):
wallet1 = WalletIntegrityHelper.create_multisig_wallet(
[
keystore.from_seed('bitter grass shiver impose acquire brush forget axis eager alone wine silver', passphrase='', for_multisig=True),
keystore.from_xpub('Vpub5fcdcgEwTJmbmqAktuK8Kyq92fMf7sWkcP6oqAii2tG47dNbfkGEGUbfS9NuZaRywLkHE6EmUksrqo32ZL3ouLN1HTar6oRiHpDzKMAF1tf'),
keystore.from_xpub('Vpub5fjkKyYnvSS4wBuakWTkNvZDaBM2vQ1MeXWq368VJHNr2eT8efqhpmZ6UUkb7s2dwCXv2Vuggjdhk4vZVyiAQTwUftvff73XcUGq2NQmWra')
],
'2of3', gap_limit=2,
config=self.config,
)
# bootstrap wallet1
funding_tx = Transaction('01000000000101a41aae475d026c9255200082c7fad26dc47771275b0afba238dccda98a597bd20000000000fdffffff02400d0300000000002200203c43ac80d6e3015cf378bf6bac0c22456723d6050bef324ec641e7762440c63c9dcd410000000000160014824626055515f3ed1d2cfc9152d2e70685c71e8f02483045022100b9f39fad57d07ce1e18251424034f21f10f20e59931041b5167ae343ce973cf602200fefb727fa0ffd25b353f1bcdae2395898fe407b692c62f5885afbf52fa06f5701210301a28f68511ace43114b674371257bb599fd2c686c4b19544870b1799c954b40e9c11300')
wallet1.adb.receive_tx_callback(funding_tx, TX_HEIGHT_UNCONFIRMED)
# wallet1 -> wallet2
tx = tx_from_any("01000000000101213e1012a461e056752fab5a6414a2fb63f950cd21a50ac5e2b82d339d6cbdd20000000000feffffff023075000000000000220020cc5e4cc05a76d0648cd0742768556317e9f8cc729aed077134287909035dba88888402000000000017a914187842cea9c15989a51ce7ca889a08b824bf8743870400473044022055cb04fa71c4b5955724d7ac5da90436d75212e7847fc121cb588f54bcdffdc4022064eca1ad639b7c748101059dc69f2893abb3b396bcf9c13f670415076f93ddbf01473044022009230e456724f2a4c10d886c836eeec599b21db0bf078aa8fc8c95868b8920ec02200dfda835a66acb5af50f0d95fcc4b76c6e8f4789a7184c182275b087d1efe556016952210223f815ab09f6bfc8519165c5232947ae89d9d43d678fb3486f3b28382a2371fa210273c529c2c9a99592f2066cebc2172a48991af2b471cb726b9df78c6497ce984e2102aa8fc578b445a1e4257be6b978fcece92980def98dce0e1eb89e7364635ae94153ae00000000")
wallet1.adb.add_transaction(tx)
# let's see if the calculated feerate correct:
self.assertEqual((3, 'Local [26.3 sat/vB]'),
wallet1.get_tx_status(tx.txid(), TxMinedInfo(height=TX_HEIGHT_LOCAL, conf=0)))
class TestImportedWallet(ElectrumTestCase): class TestImportedWallet(ElectrumTestCase):
TESTNET = True TESTNET = True
transactions = { transactions = {

Loading…
Cancel
Save