mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
* initial blocktime (tests) * regtest tx * json loads * bit more testing * complete txlist-test * internediate commit * Feature: prevent_mining for bitcoind * speedup ctrl-c * proper handling of len() == 0 * having automatic invalidation in the txlist cache * fix cypress automatic build * kick * blocktime vs. mempooltime in UI * fix blocktime * correct time for including historical pricedata * time is not a good var name * remove print-statements * work on tests * finally fix the test
This commit is contained in:
parent
dad97f55ff
commit
e33a178a21
10 changed files with 442 additions and 36 deletions
|
|
@ -7,19 +7,20 @@ import signal
|
|||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from threading import Event
|
||||
|
||||
import click
|
||||
import psutil
|
||||
from flask import Config
|
||||
|
||||
from ..config import DEFAULT_CONFIG
|
||||
from ..process_controller.node_controller import find_node_executable
|
||||
from ..process_controller.elementsd_controller import ElementsPlainController
|
||||
from ..process_controller.node_controller import find_node_executable
|
||||
from .utils import (
|
||||
Echo,
|
||||
compute_data_dir_and_set_config_obj,
|
||||
kill_node_process,
|
||||
purge_node_data_dir,
|
||||
compute_data_dir_and_set_config_obj,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -385,29 +386,46 @@ def miner_loop(node_impl, my_node, data_folder, mining_every_x_seconds, echo):
|
|||
f"height: {my_node.rpcconn.get_rpc().getblockchaininfo()['blocks']} | ",
|
||||
nl=False,
|
||||
)
|
||||
exit = Event()
|
||||
|
||||
def exit_now(signo, _frame):
|
||||
exit.set()
|
||||
|
||||
for sig in ("HUP", "INT"):
|
||||
signal.signal(getattr(signal, "SIG" + sig), exit_now)
|
||||
prevent_mining_file = Path("prevent_mining")
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
my_node.mine()
|
||||
current_height = my_node.rpcconn.get_rpc().getblockchaininfo()["blocks"]
|
||||
exit.wait(mining_every_x_seconds)
|
||||
if not prevent_mining_file.is_file():
|
||||
my_node.mine()
|
||||
else:
|
||||
echo("X", prefix=False, nl=False)
|
||||
continue
|
||||
|
||||
echo("%i" % (i % 10), prefix=False, nl=False)
|
||||
if i % 10 == 9:
|
||||
echo(" ", prefix=False, nl=False)
|
||||
i += 1
|
||||
if i >= 50:
|
||||
i = 0
|
||||
echo("", prefix=False)
|
||||
echo(
|
||||
f"height: {current_height} | ",
|
||||
nl=False,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
f"Caught {e}, Couldn't mine, assume SIGTERM occured => exiting!"
|
||||
)
|
||||
echo(f"THE_END(@height:{current_height})")
|
||||
if prevent_mining_file.is_file():
|
||||
echo("Deleting file prevent_mining")
|
||||
prevent_mining_file.unlink()
|
||||
break
|
||||
echo("%i" % (i % 10), prefix=False, nl=False)
|
||||
if i % 10 == 9:
|
||||
echo(" ", prefix=False, nl=False)
|
||||
i += 1
|
||||
if i >= 50:
|
||||
i = 0
|
||||
echo("", prefix=False)
|
||||
echo(
|
||||
f"height: {current_height} | ",
|
||||
nl=False,
|
||||
)
|
||||
time.sleep(mining_every_x_seconds)
|
||||
|
||||
|
||||
def mine_2_specter_wallets(node_impl, my_node, data_folder, echo):
|
||||
|
|
|
|||
|
|
@ -765,14 +765,18 @@ def wallet_overview_utxo_csv():
|
|||
|
||||
# Transactions list to user-friendly CSV format
|
||||
def txlist_to_csv(wallet, _txlist, specter, current_user, includePricesHistory=False):
|
||||
# Why is this line needed?
|
||||
# Please remover if you can!
|
||||
from flask_babel import lazy_gettext as _
|
||||
|
||||
txlist = []
|
||||
for tx in _txlist:
|
||||
if isinstance(tx["address"], list):
|
||||
_tx = tx.copy()
|
||||
tx = tx.copy()
|
||||
for i in range(0, len(tx["address"])):
|
||||
_tx["address"] = tx["address"][i]
|
||||
_tx["amount"] = tx["amount"][i]
|
||||
txlist.append(_tx.copy())
|
||||
tx["address"] = tx["address"][i]
|
||||
tx["amount"] = tx["amount"][i]
|
||||
txlist.append(tx.copy())
|
||||
else:
|
||||
txlist.append(tx.copy())
|
||||
data = StringIO()
|
||||
|
|
@ -825,34 +829,38 @@ def txlist_to_csv(wallet, _txlist, specter, current_user, includePricesHistory=F
|
|||
if specter.unit == "sat":
|
||||
value = float(tx["amount"])
|
||||
tx["amount"] = round(value * 1e8)
|
||||
amount_price = "not supported"
|
||||
rate = "not supported"
|
||||
if tx["blocktime"]:
|
||||
timestamp = tx["blocktime"]
|
||||
else:
|
||||
timestamp = tx["time"]
|
||||
if includePricesHistory:
|
||||
try:
|
||||
rate, _ = get_price_at(specter, current_user, timestamp=tx["time"])
|
||||
rate, _ = get_price_at(specter, current_user, timestamp)
|
||||
rate = float(rate)
|
||||
if specter.unit == "sat":
|
||||
rate = rate / 1e8
|
||||
amount_price = float(tx["amount"]) * rate
|
||||
amount_price = round(amount_price * 100) / 100
|
||||
if specter.unit == "sat":
|
||||
rate = round(1 / rate)
|
||||
except SpecterError as se:
|
||||
logger.error(se)
|
||||
success = False
|
||||
amount_price = None
|
||||
rate = "-"
|
||||
|
||||
row = (
|
||||
time.strftime("%Y-%m-%d", time.localtime(tx["time"])),
|
||||
time.strftime("%Y-%m-%d", time.localtime(timestamp)),
|
||||
label,
|
||||
tx["category"],
|
||||
round(tx["amount"], (0 if specter.unit == "sat" else 8)),
|
||||
round(amount_price * 100) / 100
|
||||
if amount_price is not None
|
||||
else "no-support",
|
||||
amount_price,
|
||||
rate,
|
||||
tx["txid"],
|
||||
tx["address"],
|
||||
tx["blockheight"],
|
||||
tx["time"],
|
||||
time,
|
||||
)
|
||||
if not wallet:
|
||||
row = (tx.get("wallet_alias", ""),) + row
|
||||
|
|
@ -954,6 +962,7 @@ def wallet_addresses_list_to_csv(addresses_list):
|
|||
|
||||
|
||||
def process_txlist(txlist, idx=0, limit=100, search=None, sortby=None, sortdir="asc"):
|
||||
"""Prepares the txlist for the ui filtering it with the search-criterias and sorting it"""
|
||||
if search:
|
||||
search_lower = search.lower()
|
||||
txlist = [
|
||||
|
|
|
|||
|
|
@ -246,7 +246,18 @@
|
|||
}
|
||||
|
||||
// Set time
|
||||
this.time.innerText = this.hideSensitiveInfo ? '###########' : (new Date(this.tx.time * 1000)).toLocaleString()
|
||||
if (this.hideSensitiveInfo) {
|
||||
this.time.innerText = '###########'
|
||||
} else {
|
||||
// blocktime for confirmed Txs
|
||||
if (this.tx.blocktime) {
|
||||
this.time.innerText = (new Date(this.tx.blocktime * 1000)).toLocaleString()
|
||||
} else {
|
||||
this.time.innerText = (new Date(this.tx.time * 1000)).toLocaleString()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Show blockhash
|
||||
if (this.showBlockhash) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ Manages the list of transactions for the wallet
|
|||
"""
|
||||
from typing import Union
|
||||
import os
|
||||
from .persistence import write_csv, read_csv
|
||||
from .specter_error import SpecterError
|
||||
from .persistence import delete_file, write_csv, read_csv
|
||||
from .helpers import get_address_from_dict
|
||||
from embit.transaction import Transaction
|
||||
from embit.liquid.networks import get_network
|
||||
|
|
@ -47,6 +48,7 @@ class TxItem(dict, AbstractTxListContext):
|
|||
"blockhash", # str, blockhash, None if not confirmed
|
||||
"blockheight", # int, blockheight, None if not confirmed
|
||||
"time", # int (timestamp in seconds), time received
|
||||
"blocktime", # int (timestamp in seconds), time the block was mined
|
||||
"bip125-replaceable", # str ("yes" / "no"), whatever RBF is enabled for the transaction
|
||||
"conflicts", # rbf conflicts, list of txids
|
||||
"vsize",
|
||||
|
|
@ -60,6 +62,7 @@ class TxItem(dict, AbstractTxListContext):
|
|||
str,
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
str,
|
||||
parse_arr,
|
||||
int,
|
||||
|
|
@ -180,6 +183,7 @@ class TxItem(dict, AbstractTxListContext):
|
|||
"blockhash": self["blockhash"],
|
||||
"blockheight": self["blockheight"],
|
||||
"time": self["time"],
|
||||
"blocktime": self["blocktime"],
|
||||
"conflicts": self["conflicts"],
|
||||
"bip125-replaceable": self["bip125-replaceable"],
|
||||
"vsize": self["vsize"],
|
||||
|
|
@ -218,7 +222,7 @@ class TxList(dict, AbstractTxListContext):
|
|||
logger.error(e)
|
||||
self._file_exists = file_exists
|
||||
|
||||
def save(self):
|
||||
def _save(self):
|
||||
# check if we have at least one transaction
|
||||
if self:
|
||||
# Dump all transactions to binary files
|
||||
|
|
@ -226,7 +230,10 @@ class TxList(dict, AbstractTxListContext):
|
|||
for tx in self.values():
|
||||
tx.dump()
|
||||
write_csv(self.path, list(self.values()), self.ItemCls)
|
||||
self._file_exists = True
|
||||
self._file_exists = True
|
||||
else:
|
||||
delete_file(self.path)
|
||||
self._file_exists = False
|
||||
|
||||
def getfetch(self, txid):
|
||||
"""
|
||||
|
|
@ -249,7 +256,10 @@ class TxList(dict, AbstractTxListContext):
|
|||
decode=True will decode transaction similar to Core's decoderawtransaction
|
||||
"""
|
||||
# if we don't know blockheigth or transaction
|
||||
# we get it from rpc
|
||||
# we invalidate which results in asking core
|
||||
if txid in self and self[txid]["blockheight"] == None:
|
||||
self.invalidate(txid)
|
||||
|
||||
if blockheight is None or txid not in self:
|
||||
tx = self.rpc.gettransaction(txid)
|
||||
if "time" not in tx:
|
||||
|
|
@ -263,10 +273,17 @@ class TxList(dict, AbstractTxListContext):
|
|||
if full:
|
||||
res["hex"] = tx.hex
|
||||
if decode:
|
||||
res.update(self.decoderawtransaction(tx.hex))
|
||||
res.update(self._decoderawtransaction(tx.hex))
|
||||
return res
|
||||
|
||||
def decoderawtransaction(self, tx: Union[Transaction, str, bytes]):
|
||||
def invalidate(self, txid):
|
||||
"""removes a tx from the list"""
|
||||
if txid not in self:
|
||||
raise SpecterError(f"TX with txid {txid} does not exit in {self}")
|
||||
del self[txid]
|
||||
self._save()
|
||||
|
||||
def _decoderawtransaction(self, tx: Union[Transaction, str, bytes]):
|
||||
return SpecterTx(self, tx).to_dict()
|
||||
|
||||
def add(self, txs):
|
||||
|
|
@ -280,6 +297,7 @@ class TxList(dict, AbstractTxListContext):
|
|||
"blockheight", - int blockheight if confirmed, None otherwise
|
||||
"blockhash", - str blockhash if confirmed, None otherwise
|
||||
"time", - int unix timestamp in seconds when tx was received
|
||||
"blocktime", - int unix timestamp in seconds the block was mined
|
||||
"conflicts", - list of txids spending the same inputs (rbf)
|
||||
"bip125-replaceable", - str ("yes" or "no") - is rbf enabled for this tx
|
||||
}
|
||||
|
|
@ -302,6 +320,7 @@ class TxList(dict, AbstractTxListContext):
|
|||
"blockheight": tx.get("blockheight", None),
|
||||
"blockhash": tx.get("blockhash", None),
|
||||
"time": time,
|
||||
"blocktime": tx.get("blocktime", None),
|
||||
"conflicts": tx.get("walletconflicts", []),
|
||||
"bip125-replaceable": tx.get("bip125-replaceable", "no"),
|
||||
"hex": tx.get("hex", None),
|
||||
|
|
@ -319,8 +338,8 @@ class TxList(dict, AbstractTxListContext):
|
|||
self._addresses.set_used(addresses)
|
||||
# detect category, amounts and addresses
|
||||
for tx in [self[txid] for txid in self if txid in txs]:
|
||||
self.fill_missing(tx)
|
||||
self.save()
|
||||
self._fill_missing(tx)
|
||||
self._save()
|
||||
|
||||
def _update_destinations(self, tx, outs):
|
||||
addresses = [out.get("address", "Unknown") for out in outs]
|
||||
|
|
@ -339,7 +358,12 @@ class TxList(dict, AbstractTxListContext):
|
|||
psbt.update(updated)
|
||||
return psbt
|
||||
|
||||
def fill_missing(self, tx):
|
||||
def _fill_missing(self, tx):
|
||||
"""This seem to calculate the category of the tx which is one of:
|
||||
mixed (default), generate, selftransfer, receive or send
|
||||
|
||||
Also the tx gets a key with a boolean to figure out whether its "mine"
|
||||
"""
|
||||
raw_tx = tx.tx
|
||||
psbt = self._get_psbt(raw_tx)
|
||||
# detect category
|
||||
|
|
|
|||
191
tests/test_txlist.py
Normal file
191
tests/test_txlist.py
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import json
|
||||
import os
|
||||
import time
|
||||
from binascii import hexlify
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from cryptoadvance.specter.process_controller.bitcoind_controller import (
|
||||
BitcoindPlainController,
|
||||
)
|
||||
from cryptoadvance.specter.txlist import TxItem, TxList
|
||||
from embit.descriptor.arguments import Key
|
||||
from embit.descriptor.descriptor import Descriptor
|
||||
from embit.transaction import Transaction, TransactionInput
|
||||
from mock import MagicMock
|
||||
|
||||
descriptor = "pkh([78738c82/84h/1h/0h]vpub5YN2RvKrA9vGAoAdpsruQGfQMWZzaGt3M5SGMMhW8i2W4SyNSHMoLtyyLLS6EjSzLfrQcbtWdQcwNS6AkCWne1Y7U8bt9JgVYxfeH9mCVPH/1/*)"
|
||||
# The example transaction from a regtest
|
||||
|
||||
# 42f5c9e826e52cde883cde7a6c7b768db302e0b8b32fc52db75ad3c5711b4a9e
|
||||
with open("tests/xtestdata_txlist/tx1_confirmed.json") as f:
|
||||
tx1_confirmed = json.load(f)
|
||||
|
||||
#
|
||||
with open("tests/xtestdata_txlist/tx2_unconfirmed.json") as f:
|
||||
tx2_unconfirmed = json.load(f)
|
||||
|
||||
with open("tests/xtestdata_txlist/tx2_confirmed.json") as f:
|
||||
tx2_confirmed = json.load(f)
|
||||
|
||||
with open("tests/xtestdata_txlist/tx2_confirmed2.json") as f:
|
||||
tx2_confirmed2 = json.load(f)
|
||||
|
||||
|
||||
def test_understandTransaction():
|
||||
mytx = Transaction.from_string(tx1_confirmed["hex"])
|
||||
assert mytx.version == 2
|
||||
assert mytx.locktime == 415
|
||||
assert type(mytx.vin[0]) == TransactionInput
|
||||
assert (
|
||||
hexlify(mytx.vin[0].txid)
|
||||
== b"c7c9dd852fa9cbe72b2f6e3b2eeba1a2b47dc4422b3719a55381be8010d7993f"
|
||||
)
|
||||
assert mytx.vout[0].value == 1999999890
|
||||
assert (
|
||||
hexlify(mytx.txid())
|
||||
== b"42f5c9e826e52cde883cde7a6c7b768db302e0b8b32fc52db75ad3c5711b4a9e"
|
||||
)
|
||||
|
||||
|
||||
def test_TxItem(empty_data_folder):
|
||||
mytxitem = TxItem(
|
||||
None,
|
||||
[],
|
||||
empty_data_folder,
|
||||
hex=tx1_confirmed["hex"],
|
||||
blocktime=1642182445, # arbitrary stuff can get passed
|
||||
)
|
||||
# a TxItem pretty much works like a hash with some extrafunctionality
|
||||
assert mytxitem["blocktime"] == 1642182445
|
||||
# We can also add data after the fact
|
||||
mytxitem["confirmations"] = 123
|
||||
assert mytxitem.tx.vout
|
||||
assert mytxitem["confirmations"] == 123
|
||||
|
||||
|
||||
def test_txlist(empty_data_folder, bitcoin_regtest):
|
||||
parent_mock = MagicMock()
|
||||
bitcoin_regtest.get_rpc().createwallet("txlist1")
|
||||
wrpc = bitcoin_regtest.get_rpc().wallet("txlist1")
|
||||
parent_mock.rpc = wrpc
|
||||
|
||||
parent_mock.descriptor = Descriptor.from_string(descriptor)
|
||||
assert type(parent_mock.descriptor.key) == Key
|
||||
assert parent_mock.descriptor.key.allowed_derivation != None
|
||||
assert parent_mock.descriptor.to_string() == descriptor
|
||||
filename = os.path.join(empty_data_folder, "my_filename.csv")
|
||||
mytxlist = TxList(filename, parent_mock, MagicMock())
|
||||
# mytxlist.descriptor = descriptor
|
||||
mytxlist.add({tx1_confirmed["txid"]: tx1_confirmed})
|
||||
# .add will save implicitely.
|
||||
# mytxlist._save()
|
||||
with open(filename, "r+") as file:
|
||||
# Reading form a file
|
||||
|
||||
assert file.readline().startswith(
|
||||
"txid,blockhash,blockheight,time,blocktime,bip125-replaceable,conflicts,vsize,category,address,amount,ismine"
|
||||
)
|
||||
assert file.readline().startswith(
|
||||
"42f5c9e826e52cde883cde7a6c7b768db302e0b8b32fc52db75ad3c5711b4a9e,72523c637e0b93505806564495b1acf915a88bacc45f50e35e8a536becd2f914,,1642494258,1642494258,no,[],,receive,Unknown,19.9999989,False"
|
||||
)
|
||||
assert len(mytxlist) == 1
|
||||
mytxlist.invalidate(tx1_confirmed["txid"])
|
||||
assert len(mytxlist) == 0
|
||||
assert not Path(filename).is_file()
|
||||
|
||||
# Mock rpc-calls
|
||||
mock_rpc = MagicMock()
|
||||
mock_rpc.gettransaction.return_value = tx2_confirmed
|
||||
mock_parent = MagicMock()
|
||||
mock_parent.rpc = mock_rpc
|
||||
mytxlist.parent = mock_parent
|
||||
# mytxlist.getfetch("42f5c9e826e52cde883cde7a6c7b768db302e0b8b32fc52db75ad3c5711b4a9e")
|
||||
|
||||
# assert False
|
||||
|
||||
|
||||
def test_txlist_invalidate(empty_data_folder, bitcoin_regtest: BitcoindPlainController):
|
||||
def print_time():
|
||||
curr_dt = datetime.now()
|
||||
print(f"current time\t\t\t\t\t: {int(round(curr_dt.timestamp()))}\n")
|
||||
|
||||
# Setup the infra
|
||||
rpc = bitcoin_regtest.get_rpc()
|
||||
rpc.mine
|
||||
rpc.createwallet("txlist2")
|
||||
wrpc = rpc.wallet("txlist2")
|
||||
address = wrpc.getnewaddress()
|
||||
bitcoin_regtest.testcoin_faucet(address, amount=10, confirm_payment=True)
|
||||
# itcoin_regtest.mine(self, address=address, block_count=100)
|
||||
|
||||
# Create the mytxlist
|
||||
parent_mock = MagicMock()
|
||||
parent_mock.rpc = wrpc
|
||||
parent_mock.descriptor = Descriptor.from_string(descriptor)
|
||||
assert type(parent_mock.descriptor.key) == Key
|
||||
assert parent_mock.descriptor.key.allowed_derivation != None
|
||||
assert parent_mock.descriptor.to_string() == descriptor
|
||||
filename = os.path.join(empty_data_folder, "my_filename.csv")
|
||||
mytxlist = TxList(filename, parent_mock, MagicMock())
|
||||
|
||||
# create a tx
|
||||
|
||||
txid = wrpc.sendtoaddress("bcrt1qsj30deg0fgzckvlrn5757yk55yajqv6dqx0x7u", "1")
|
||||
tx_via_core = wrpc.gettransaction(txid)
|
||||
print_time()
|
||||
assert tx_via_core, "we have created a transaction"
|
||||
assert tx_via_core["confirmations"] == 0, "it's not confirmed, yet"
|
||||
print(f"TxId: {txid}")
|
||||
print(f"Tx via core:\n{tx_via_core}\n")
|
||||
assert (
|
||||
tx_via_core["time"] == tx_via_core["timereceived"]
|
||||
), "time and timereceived should be the same"
|
||||
print(f"timereceived \t\t\t\t\t: {tx_via_core['time']}")
|
||||
print_time()
|
||||
|
||||
tx_item = mytxlist.getfetch(txid)
|
||||
assert isinstance(tx_item, TxItem), "The tx should get returned via the txlist"
|
||||
assert (
|
||||
mytxlist[txid] == tx_item
|
||||
), "is should be the same item you get via obtaining it via the dict-method"
|
||||
|
||||
# analyzing the content of the tx
|
||||
tx = mytxlist.gettransaction(txid)
|
||||
print(f"Tx via cache:\n{tx}\n")
|
||||
mpool_hittime = tx["time"]
|
||||
assert tx["blockheight"] == None
|
||||
print(f"this is the time when the tx hit the mempool {mpool_hittime}")
|
||||
print_time()
|
||||
print("let's sleep for 2 seconds")
|
||||
time.sleep(2)
|
||||
print_time()
|
||||
# Get the tx confirmed
|
||||
|
||||
bitcoin_regtest.mine(block_count=1)
|
||||
print("\n-----------------------mining----------------------------------\n")
|
||||
tx_via_core = wrpc.gettransaction(txid)
|
||||
assert tx_via_core["confirmations"] == 1, "it should now be broadcasted"
|
||||
print(f"Tx via core:\n{tx_via_core}\n")
|
||||
print(f"blocktime \t\t\t\t\t: {tx_via_core['blocktime']}")
|
||||
print_time()
|
||||
print(
|
||||
f"Difference of blocktime - current-time:\t\t\t {int(round(datetime.now().timestamp())) - tx_via_core['blocktime']}"
|
||||
)
|
||||
print(f"How is it possible that this number is negative?")
|
||||
|
||||
assert (
|
||||
tx_via_core["blocktime"] > tx_via_core["time"]
|
||||
), "blocktime should be larger than time"
|
||||
|
||||
# optional: invalidate
|
||||
# mytxlist.invalidate(txid)
|
||||
|
||||
# get it again
|
||||
tx = mytxlist.gettransaction(txid)
|
||||
print(f"Tx via cache:\n{tx}\n")
|
||||
assert tx["blockheight"], "The tx should now have a blockheight"
|
||||
assert tx["blocktime"], "The tx should now have a blocktime"
|
||||
assert (
|
||||
tx["blocktime"] > mpool_hittime
|
||||
), "The time of the transaction should now be bigger than the time it got hit the mempool"
|
||||
54
tests/test_wallet.py
Normal file
54
tests/test_wallet.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import json, logging, pytest, time, os
|
||||
from cryptoadvance.specter.specter import Specter
|
||||
from cryptoadvance.specter.managers.wallet_manager import WalletManager
|
||||
from cryptoadvance.specter.wallet import Wallet
|
||||
from conftest import instantiate_bitcoind_controller
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def test_import_address_labels(caplog, specter_regtest_configured):
|
||||
caplog.set_level(logging.INFO)
|
||||
|
||||
specter = specter_regtest_configured
|
||||
# Create a new device that can sign psbts (Bitcoin Core hot wallet)
|
||||
device = specter.device_manager.add_device(
|
||||
name="bitcoin_core_hot_wallet", device_type="bitcoincore", keys=[]
|
||||
)
|
||||
device.setup_device(file_password=None, wallet_manager=specter.wallet_manager)
|
||||
device.add_hot_wallet_keys(
|
||||
mnemonic="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
||||
passphrase="",
|
||||
paths=["m/49h/0h/0h"],
|
||||
file_password=None,
|
||||
wallet_manager=specter.wallet_manager,
|
||||
testnet=True,
|
||||
keys_range=[0, 1000],
|
||||
keys_purposes=[],
|
||||
)
|
||||
|
||||
wallet: Wallet = specter.wallet_manager.create_wallet(
|
||||
"bitcoincore_test_wallet", 1, "sh-wpkh", [device.keys[0]], [device]
|
||||
)
|
||||
|
||||
# Fund the wallet. Going to need a LOT of utxos to play with.
|
||||
logger.info("Generating utxos to wallet")
|
||||
test_address = wallet.getnewaddress() # 2NCSZrX49HHyzUy6oj8ggm9WD19hFvjzzou
|
||||
|
||||
wallet.rpc.generatetoaddress(1, test_address)[0]
|
||||
|
||||
# newly minted coins need 100 blocks to get spendable
|
||||
# let's mine another 100 blocks to get these coins spendable
|
||||
trash_address = wallet.getnewaddress()
|
||||
wallet.rpc.generatetoaddress(100, trash_address)
|
||||
|
||||
# the utxo is only available after the 100 mined blocks
|
||||
utxos = wallet.rpc.listunspent()
|
||||
# txid of the funding of test_address
|
||||
txid = utxos[0]["txid"]
|
||||
|
||||
assert wallet._addresses[test_address]["label"] is None
|
||||
number_of_addresses = len(wallet._addresses)
|
||||
|
||||
assert wallet.txlist()[0]["blockheight"] != None
|
||||
assert wallet.txlist()[0]["blocktime"] != None
|
||||
45
tests/xtestdata_txlist/tx1_confirmed.json
Normal file
45
tests/xtestdata_txlist/tx1_confirmed.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"in_active_chain": true,
|
||||
"txid": "42f5c9e826e52cde883cde7a6c7b768db302e0b8b32fc52db75ad3c5711b4a9e",
|
||||
"hash": "d93c51fe9b7d001a795e98dd1357de42647cda08fe5072703f7e438154402bb6",
|
||||
"version": 2,
|
||||
"size": 191,
|
||||
"vsize": 110,
|
||||
"weight": 437,
|
||||
"locktime": 415,
|
||||
"vin": [
|
||||
{
|
||||
"txid": "c7c9dd852fa9cbe72b2f6e3b2eeba1a2b47dc4422b3719a55381be8010d7993f",
|
||||
"vout": 0,
|
||||
"scriptSig": {
|
||||
"asm": "",
|
||||
"hex": ""
|
||||
},
|
||||
"txinwitness": [
|
||||
"304402201088bfd110dd891b7f16c5d50d10e6f99cf79d48673d890e3563f8275500315b02205a75e89f794a3e681fe6a7622c642323ac3c802b5f60da2547b2589982795a2401",
|
||||
"02578993563d2d00cf1047011fe77a07181a1ab0467044d9b12857c3df65653a50"
|
||||
],
|
||||
"sequence": 4294967294
|
||||
}
|
||||
],
|
||||
"vout": [
|
||||
{
|
||||
"value": 19.99999890,
|
||||
"n": 0,
|
||||
"scriptPubKey": {
|
||||
"asm": "0 84a2f6e50f4a058b33e39d3d4f12d4a13b20334d",
|
||||
"hex": "001484a2f6e50f4a058b33e39d3d4f12d4a13b20334d",
|
||||
"reqSigs": 1,
|
||||
"type": "witness_v0_keyhash",
|
||||
"addresses": [
|
||||
"bcrt1qsj30deg0fgzckvlrn5757yk55yajqv6dqx0x7u"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"hex": "020000000001013f99d71080be8153a519372b42c47db4a2a1eb2e3b6e2f2be7cba92f85ddc9c70000000000feffffff01929335770000000016001484a2f6e50f4a058b33e39d3d4f12d4a13b20334d0247304402201088bfd110dd891b7f16c5d50d10e6f99cf79d48673d890e3563f8275500315b02205a75e89f794a3e681fe6a7622c642323ac3c802b5f60da2547b2589982795a24012102578993563d2d00cf1047011fe77a07181a1ab0467044d9b12857c3df65653a509f010000",
|
||||
"blockhash": "72523c637e0b93505806564495b1acf915a88bacc45f50e35e8a536becd2f914",
|
||||
"confirmations": 222,
|
||||
"time": 1642494258,
|
||||
"blocktime": 1642494258
|
||||
}
|
||||
18
tests/xtestdata_txlist/tx2_confirmed.json
Normal file
18
tests/xtestdata_txlist/tx2_confirmed.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"amount": 0.00000000,
|
||||
"fee": -0.00000141,
|
||||
"confirmations": 1,
|
||||
"blockhash": "14859527b8638b2d051dd9541f750abf6ae9d973289c9d8eca3f3c04ddbbffc2",
|
||||
"blockheight": 2271,
|
||||
"blockindex": 1,
|
||||
"blocktime": 1645012450,
|
||||
"txid": "9dbbce06fed2f949660240afce2bb18414ea647952681fd1021693fb2d7e30db",
|
||||
"walletconflicts": [
|
||||
],
|
||||
"time": 1645012282,
|
||||
"timereceived": 1645012282,
|
||||
"bip125-replaceable": "no",
|
||||
"details": [
|
||||
],
|
||||
"hex": "02000000000101d4103f45654a10290d2f735fa78ec9368bbcc5ab1fd010b82e54c1ec00aad13f0100000000fdffffff0273b23f7100000000160014c04bf2046eb41344c5e2b5b7b2d26a5ed0cbdde100e1f505000000001600143f2593534164f23051edce61035a5b135bea69390247304402201fda244457566606106f8e36efb0a6f041c4ed84c82fb6823ad680d7ef8efacf022069978308a7f1fb65e213fa4f4294ab18533fbbe5194a2c192ee718aef9a7cb2c0121024f0c2c509e22e8d934541315f6bf816c6ca72dc19bf8aa50e02faa37aad0ff46dd080000"
|
||||
}
|
||||
18
tests/xtestdata_txlist/tx2_confirmed2.json
Normal file
18
tests/xtestdata_txlist/tx2_confirmed2.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"amount": 0.00000000,
|
||||
"fee": -0.00000141,
|
||||
"confirmations": 5,
|
||||
"blockhash": "14859527b8638b2d051dd9541f750abf6ae9d973289c9d8eca3f3c04ddbbffc2",
|
||||
"blockheight": 2271,
|
||||
"blockindex": 1,
|
||||
"blocktime": 1645012450,
|
||||
"txid": "9dbbce06fed2f949660240afce2bb18414ea647952681fd1021693fb2d7e30db",
|
||||
"walletconflicts": [
|
||||
],
|
||||
"time": 1645012282,
|
||||
"timereceived": 1645012282,
|
||||
"bip125-replaceable": "no",
|
||||
"details": [
|
||||
],
|
||||
"hex": "02000000000101d4103f45654a10290d2f735fa78ec9368bbcc5ab1fd010b82e54c1ec00aad13f0100000000fdffffff0273b23f7100000000160014c04bf2046eb41344c5e2b5b7b2d26a5ed0cbdde100e1f505000000001600143f2593534164f23051edce61035a5b135bea69390247304402201fda244457566606106f8e36efb0a6f041c4ed84c82fb6823ad680d7ef8efacf022069978308a7f1fb65e213fa4f4294ab18533fbbe5194a2c192ee718aef9a7cb2c0121024f0c2c509e22e8d934541315f6bf816c6ca72dc19bf8aa50e02faa37aad0ff46dd080000"
|
||||
}
|
||||
18
tests/xtestdata_txlist/tx2_unconfirmed.json
Normal file
18
tests/xtestdata_txlist/tx2_unconfirmed.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"amount": 0.00000000,
|
||||
"fee": -0.00000141,
|
||||
"confirmations": 1,
|
||||
"blockhash": "14859527b8638b2d051dd9541f750abf6ae9d973289c9d8eca3f3c04ddbbffc2",
|
||||
"blockheight": 2271,
|
||||
"blockindex": 1,
|
||||
"blocktime": 1645012450,
|
||||
"txid": "9dbbce06fed2f949660240afce2bb18414ea647952681fd1021693fb2d7e30db",
|
||||
"walletconflicts": [
|
||||
],
|
||||
"time": 1645012282,
|
||||
"timereceived": 1645012282,
|
||||
"bip125-replaceable": "no",
|
||||
"details": [
|
||||
],
|
||||
"hex": "02000000000101d4103f45654a10290d2f735fa78ec9368bbcc5ab1fd010b82e54c1ec00aad13f0100000000fdffffff0273b23f7100000000160014c04bf2046eb41344c5e2b5b7b2d26a5ed0cbdde100e1f505000000001600143f2593534164f23051edce61035a5b135bea69390247304402201fda244457566606106f8e36efb0a6f041c4ed84c82fb6823ad680d7ef8efacf022069978308a7f1fb65e213fa4f4294ab18533fbbe5194a2c192ee718aef9a7cb2c0121024f0c2c509e22e8d934541315f6bf816c6ca72dc19bf8aa50e02faa37aad0ff46dd080000"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue