bitcoinwatcher 2.4__py3-none-any.whl → 2.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bitcoin/address_listener/address_listener.py +5 -4
- bitcoin/models/address_tx_data.py +1 -1
- bitcoin/tx_extractors/bitcoin_rpc.py +7 -5
- bitcoin/tx_extractors/default.py +3 -3
- bitcoin/tx_listener/zmq_listener.py +3 -1
- bitcoin/utils/benchmark.py +0 -2
- bitcoin/utils/bitcoin_rpc.py +14 -4
- bitcoin/utils/context_aware_logging.py +54 -0
- {bitcoinwatcher-2.4.dist-info → bitcoinwatcher-2.6.dist-info}/METADATA +14 -13
- {bitcoinwatcher-2.4.dist-info → bitcoinwatcher-2.6.dist-info}/RECORD +13 -12
- {bitcoinwatcher-2.4.dist-info → bitcoinwatcher-2.6.dist-info}/WHEEL +1 -1
- {bitcoinwatcher-2.4.dist-info → bitcoinwatcher-2.6.dist-info}/LICENSE +0 -0
- {bitcoinwatcher-2.4.dist-info → bitcoinwatcher-2.6.dist-info}/top_level.txt +0 -0
@@ -7,9 +7,9 @@ from ordipool.ordipool.mempoolio import Mempool
|
|
7
7
|
from bitcoin.models.address_tx_data import AddressTxData
|
8
8
|
from bitcoin.tx_extractors.bitcoin_rpc import BitcoinRPCAddressDataExtractor
|
9
9
|
from bitcoin.tx_extractors.default import DefaultTxAddressDataExtractor
|
10
|
-
from bitcoin.tx_extractors.mempool import MempoolTxAddressDataExtractor
|
11
10
|
from bitcoin.tx_listener.abstract_tx_listener import AbstractTxListener
|
12
11
|
from bitcoin.utils.constants import default_host
|
12
|
+
from bitcoin.utils.context_aware_logging import logger
|
13
13
|
|
14
14
|
|
15
15
|
class AbstractAddressListener(AbstractTxListener, ABC):
|
@@ -18,7 +18,7 @@ class AbstractAddressListener(AbstractTxListener, ABC):
|
|
18
18
|
base_url = f"http://{host}:3006/api"
|
19
19
|
mempool = Mempool(base_url=base_url)
|
20
20
|
default_tx_extractor = DefaultTxAddressDataExtractor()
|
21
|
-
|
21
|
+
rpc_tx_extractor = BitcoinRPCAddressDataExtractor()
|
22
22
|
|
23
23
|
def __init__(self, addresses_to_listen: {str}):
|
24
24
|
self.addresses_to_listen = addresses_to_listen
|
@@ -44,9 +44,10 @@ class AbstractAddressListener(AbstractTxListener, ABC):
|
|
44
44
|
return
|
45
45
|
# get the address tx data from mempool for full details if any address matches
|
46
46
|
try:
|
47
|
-
|
47
|
+
logger.info(f"Extracting rpc tx data")
|
48
|
+
address_tx_data = self.rpc_tx_extractor.extract(tx)
|
48
49
|
except Exception as e:
|
49
|
-
|
50
|
+
logger.error(f"Error in getting rpc tx data, taking defaults",e, exc_info=True)
|
50
51
|
address_tx_data = address_tx_data
|
51
52
|
|
52
53
|
for address in addresses_for_events:
|
@@ -7,6 +7,7 @@ from bitcoin.address_listener.address_listener import AddressTxData
|
|
7
7
|
from bitcoin.models.address_tx_data import AddressTxType
|
8
8
|
from bitcoin.utils.bitcoin_rpc import BitcoinRPC
|
9
9
|
from bitcoin.tx_extractors.abstract_extractor import AbstractTxAddressDataExtractor
|
10
|
+
from bitcoin.utils.context_aware_logging import logger
|
10
11
|
|
11
12
|
|
12
13
|
class BitcoinRPCAddressDataExtractor(AbstractTxAddressDataExtractor):
|
@@ -28,7 +29,7 @@ class BitcoinRPCAddressDataExtractor(AbstractTxAddressDataExtractor):
|
|
28
29
|
def get_address_tx_from_inputdata(self, tx_id, tx_status, input_data):
|
29
30
|
address = input_data["scriptPubKey"]["address"]
|
30
31
|
amount = int(input_data.get("value", 0).real * 100000000)
|
31
|
-
return AddressTxData(tx_id=tx_id,
|
32
|
+
return AddressTxData(tx_id=tx_id, is_confirmed=tx_status, address=address, _amount=amount,
|
32
33
|
type=AddressTxType.INPUT)
|
33
34
|
|
34
35
|
def extract(self, tx: Transaction) -> [AddressTxData]:
|
@@ -38,23 +39,24 @@ class BitcoinRPCAddressDataExtractor(AbstractTxAddressDataExtractor):
|
|
38
39
|
inputs = tx.inputs
|
39
40
|
# bulk get all the inputs from BitcoinRPC using thread pool
|
40
41
|
inputs_data = self.fetch_all_inputs(inputs)
|
41
|
-
|
42
|
+
is_confirmed = self.bitcoinrpc.is_confirmed(tx_id)
|
43
|
+
logger.info("Transaction is_confirmed: ", is_confirmed)
|
42
44
|
for input in inputs:
|
43
45
|
address = input.address
|
44
46
|
amount = 0
|
45
47
|
if len(inputs_data) > 0:
|
46
48
|
input_data = inputs_data.pop(0)
|
47
|
-
input_tx_data = self.get_address_tx_from_inputdata(tx_id,
|
49
|
+
input_tx_data = self.get_address_tx_from_inputdata(tx_id, is_confirmed, input_data)
|
48
50
|
address_tx_data.append(input_tx_data)
|
49
51
|
else:
|
50
|
-
address_tx_data.append(AddressTxData(
|
52
|
+
address_tx_data.append(AddressTxData(is_confirmed=is_confirmed,
|
51
53
|
address=address,
|
52
54
|
type=AddressTxType.INPUT,
|
53
55
|
_amount=amount,
|
54
56
|
tx_id=tx.txid))
|
55
57
|
for output in outputs:
|
56
58
|
amount = output.value
|
57
|
-
address_tx_data.append(AddressTxData(
|
59
|
+
address_tx_data.append(AddressTxData(is_confirmed=is_confirmed,
|
58
60
|
address=output.address,
|
59
61
|
_amount=amount,
|
60
62
|
type=AddressTxType.OUTPUT,
|
bitcoin/tx_extractors/default.py
CHANGED
@@ -10,18 +10,18 @@ class DefaultTxAddressDataExtractor(AbstractTxAddressDataExtractor):
|
|
10
10
|
outputs = tx.outputs
|
11
11
|
address_tx_data = []
|
12
12
|
inputs = tx.inputs
|
13
|
-
|
13
|
+
is_confirmed = tx.status == "confirmed"
|
14
14
|
for input in inputs:
|
15
15
|
address = input.address
|
16
16
|
amount = 0
|
17
|
-
address_tx_data.append(AddressTxData(
|
17
|
+
address_tx_data.append(AddressTxData(is_confirmed=is_confirmed,
|
18
18
|
address=address,
|
19
19
|
type=AddressTxType.INPUT,
|
20
20
|
_amount=amount,
|
21
21
|
tx_id=tx.txid))
|
22
22
|
for output in outputs:
|
23
23
|
amount = output.value
|
24
|
-
address_tx_data.append(AddressTxData(
|
24
|
+
address_tx_data.append(AddressTxData(is_confirmed=is_confirmed,
|
25
25
|
address=output.address,
|
26
26
|
_amount=amount,
|
27
27
|
type=AddressTxType.OUTPUT,
|
@@ -5,6 +5,7 @@ from bitcoinlib.transactions import Transaction
|
|
5
5
|
|
6
6
|
from bitcoin.tx_listener.abstract_tx_listener import AbstractTxListener
|
7
7
|
from bitcoin.utils.constants import default_host, default_port
|
8
|
+
from bitcoin.utils.context_aware_logging import ctx_tx, logger
|
8
9
|
|
9
10
|
|
10
11
|
class ZMQTXListener:
|
@@ -20,12 +21,13 @@ class ZMQTXListener:
|
|
20
21
|
|
21
22
|
def start(self):
|
22
23
|
connections = self.socket.connect(self.zmq_url)
|
23
|
-
|
24
|
+
logger.info(f"Connected to {connections}")
|
24
25
|
self.socket.setsockopt_string(zmq.SUBSCRIBE, "rawtx")
|
25
26
|
while True:
|
26
27
|
topic, body, seq = self.socket.recv_multipart()
|
27
28
|
try:
|
28
29
|
tx = Transaction.parse(body, strict=False)
|
30
|
+
ctx_tx.set(tx.txid)
|
29
31
|
self.tx_listener.on_tx(tx)
|
30
32
|
except Exception as e:
|
31
33
|
print(f"Error in parsing tx: {e}")
|
bitcoin/utils/benchmark.py
CHANGED
@@ -18,7 +18,6 @@ if __name__ == '__main__':
|
|
18
18
|
txid = 'a6293e898b056fbea0329d071b1b237e4449ff464cdbc7a9ed8a770b97aafd4c'
|
19
19
|
times = 1000
|
20
20
|
# start = time.time()
|
21
|
-
# print("mempool starting")
|
22
21
|
# for i in range(times):
|
23
22
|
# print(i)
|
24
23
|
# mempool.get_transaction(txid)
|
@@ -29,5 +28,4 @@ if __name__ == '__main__':
|
|
29
28
|
for i in range(times):
|
30
29
|
tx = bitcoinrpc.get_transaction(txid)
|
31
30
|
end = time.time()
|
32
|
-
print(f"bitcoinrpc took: {end - start}")
|
33
31
|
|
bitcoin/utils/bitcoin_rpc.py
CHANGED
@@ -3,6 +3,7 @@ import os
|
|
3
3
|
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
|
4
4
|
|
5
5
|
from bitcoin.utils.constants import default_host
|
6
|
+
from bitcoin.utils.context_aware_logging import ctx_tx, ctx_tx_status, logger, get_logger
|
6
7
|
|
7
8
|
|
8
9
|
class BitcoinRPC:
|
@@ -18,12 +19,21 @@ class BitcoinRPC:
|
|
18
19
|
def get_transaction(self, txid: str) -> dict:
|
19
20
|
return self.rpc_connection.getrawtransaction(txid, True)
|
20
21
|
|
22
|
+
def is_confirmed(self, txid: str) -> bool:
|
23
|
+
try:
|
24
|
+
self.rpc_connection.getmempoolentry(txid)
|
25
|
+
return False
|
26
|
+
except JSONRPCException as e:
|
27
|
+
return True
|
28
|
+
|
21
29
|
|
22
30
|
if __name__ == '__main__':
|
31
|
+
logger = get_logger(__name__)
|
23
32
|
rpc = BitcoinRPC()
|
24
|
-
txid = '
|
33
|
+
txid = '686d025f16d9f20353665a9d865e575e3e4d14214f6f7045149a17dd6bf0fac6'
|
25
34
|
try:
|
26
|
-
|
27
|
-
|
35
|
+
ctx_tx.set('686d025f16d9f20353665a9d865e575e3e4d14214f6f7045149a17dd6bf0fac6')
|
36
|
+
ctx_tx_status.set('confirmed')
|
37
|
+
logger.info("Transaction is confirmed")
|
28
38
|
except JSONRPCException as e:
|
29
|
-
print(f"An error occurred: {e}")
|
39
|
+
print(f"An error occurred: {e}")
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import logging
|
2
|
+
from contextvars import ContextVar
|
3
|
+
from datetime import datetime
|
4
|
+
|
5
|
+
import pytz
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
root = logging.getLogger()
|
9
|
+
root.setLevel(logging.INFO)
|
10
|
+
|
11
|
+
class TimezoneFormatter(logging.Formatter):
|
12
|
+
def __init__(self, fmt=None, datefmt=None, tz=None):
|
13
|
+
super().__init__(fmt, datefmt)
|
14
|
+
self.tz = tz
|
15
|
+
|
16
|
+
def formatTime(self, record, datefmt=None):
|
17
|
+
dt = datetime.fromtimestamp(record.created, self.tz)
|
18
|
+
if datefmt:
|
19
|
+
s = dt.strftime(datefmt)
|
20
|
+
else:
|
21
|
+
s = dt.isoformat()
|
22
|
+
return s
|
23
|
+
|
24
|
+
|
25
|
+
formatter = TimezoneFormatter(
|
26
|
+
' %(asctime)s %(name)s txid=%(tx_id)s [%(module)s:%(lineno)d] %(message)s',
|
27
|
+
datefmt='%Y-%m-%d %H:%M:%S',
|
28
|
+
tz=pytz.timezone('Asia/Kolkata')
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
ctx_tx = ContextVar('tx_id', default='')
|
33
|
+
ctx_tx_status = ContextVar('tx_status', default='')
|
34
|
+
|
35
|
+
class TXContextFilter(logging.Filter):
|
36
|
+
|
37
|
+
def __init__(self):
|
38
|
+
super().__init__()
|
39
|
+
|
40
|
+
def filter(self, record):
|
41
|
+
tx_id = ctx_tx.get()
|
42
|
+
record.tx_id = tx_id
|
43
|
+
tx_status = ctx_tx_status.get()
|
44
|
+
record.tx_status = tx_status
|
45
|
+
return True
|
46
|
+
|
47
|
+
ch = logging.StreamHandler()
|
48
|
+
f = TXContextFilter()
|
49
|
+
ch.setFormatter(formatter)
|
50
|
+
ch.addFilter(f)
|
51
|
+
root.addHandler(ch)
|
52
|
+
|
53
|
+
def get_logger(name):
|
54
|
+
return logging.getLogger(name)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: bitcoinwatcher
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.6
|
4
4
|
Summary: bitcoinwatcher is a Python library that implements a ZMQ subscriber and provides abstractions to build custom address watchers. This library is designed to make it easy for developers to monitor Bitcoin addresses and react to changes in their state.
|
5
5
|
Author: twosatsmaxi
|
6
|
-
License:
|
6
|
+
License: Apache License
|
7
7
|
Version 2.0, January 2004
|
8
8
|
http://www.apache.org/licenses/
|
9
9
|
|
@@ -217,17 +217,18 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
217
217
|
Requires-Python: >=3.10
|
218
218
|
Description-Content-Type: text/markdown
|
219
219
|
License-File: LICENSE
|
220
|
-
Requires-Dist: requests
|
221
|
-
Requires-Dist: certifi
|
222
|
-
Requires-Dist: charset-normalizer
|
223
|
-
Requires-Dist: idna
|
224
|
-
Requires-Dist: pyzmq
|
225
|
-
Requires-Dist: requests
|
226
|
-
Requires-Dist:
|
227
|
-
Requires-Dist:
|
228
|
-
Requires-Dist:
|
229
|
-
Requires-Dist:
|
230
|
-
Requires-Dist: python-bitcoinrpc
|
220
|
+
Requires-Dist: requests~=2.31.0
|
221
|
+
Requires-Dist: certifi==2024.2.2
|
222
|
+
Requires-Dist: charset-normalizer==3.3.2
|
223
|
+
Requires-Dist: idna==3.7
|
224
|
+
Requires-Dist: pyzmq==25.1.2
|
225
|
+
Requires-Dist: requests==2.31.0
|
226
|
+
Requires-Dist: urllib3==2.2.1
|
227
|
+
Requires-Dist: bitcoinlib==0.7.0
|
228
|
+
Requires-Dist: ordipool==1.2.0
|
229
|
+
Requires-Dist: pyzmq==25.1.2
|
230
|
+
Requires-Dist: python-bitcoinrpc==1.0
|
231
|
+
Requires-Dist: pytz~=2024.2
|
231
232
|
|
232
233
|
# bitcoinwatcher
|
233
234
|
|
@@ -1,9 +1,9 @@
|
|
1
1
|
bitcoin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
bitcoin/address_listener/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
bitcoin/address_listener/address_listener.py,sha256=
|
3
|
+
bitcoin/address_listener/address_listener.py,sha256=TkSB51U0WTfdbBnFFMXi5EK673USjvdQoytx2vWKKMk,2395
|
4
4
|
bitcoin/address_listener/simple_address_listener.py,sha256=WG8eTrd3AdkrIs1TAGRsXe_erAtE_Ep8hidZDksGk8g,1625
|
5
5
|
bitcoin/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
bitcoin/models/address_tx_data.py,sha256=
|
6
|
+
bitcoin/models/address_tx_data.py,sha256=KSSCzAjYxLEgXIELVSsnSEhgY65xi0K3HUjoamVV_LQ,750
|
7
7
|
bitcoin/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
bitcoin/tests/test_address_listener.py,sha256=USK3BMGnRsTBmCXcbnC9WkX6kF-W_sHt7RKARv39IDg,3125
|
9
9
|
bitcoin/tests/test_bitcoin_rpc.py,sha256=Xt9Mh71YQqE_gskgLMfPLs0J0YcCT7z5UimT42LOris,4137
|
@@ -12,20 +12,21 @@ bitcoin/tests/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
12
12
|
bitcoin/tests/data/transactions.py,sha256=qIX9seGYguQ1lvLOCF8oe3NQhMJZP0SsBmz1iFbszhg,396279
|
13
13
|
bitcoin/tx_extractors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
bitcoin/tx_extractors/abstract_extractor.py,sha256=YLnuqc3jva15NpOX8xV4oB5PEXc6rXbG8Lx0cBXW66s,291
|
15
|
-
bitcoin/tx_extractors/bitcoin_rpc.py,sha256=
|
16
|
-
bitcoin/tx_extractors/default.py,sha256=
|
15
|
+
bitcoin/tx_extractors/bitcoin_rpc.py,sha256=btxz7jr95SG8IuTg4klRT5OMvyVm5B3pO_-e50_MEiY,2966
|
16
|
+
bitcoin/tx_extractors/default.py,sha256=Xd09vldoFIlZo2g25KQeWbPKw7Cyj6vp40AKeDn3nJM,1407
|
17
17
|
bitcoin/tx_extractors/mempool.py,sha256=mPPY1GOvBmZWn7UVP2sUaXtfcyeTCj-LmRDl2F4WhC8,2090
|
18
18
|
bitcoin/tx_listener/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
bitcoin/tx_listener/abstract_tx_listener.py,sha256=rGp9na-aOSl_aPaIqf44RTZaFF3SvrxIhZBujTnWqao,189
|
20
|
-
bitcoin/tx_listener/zmq_listener.py,sha256=
|
20
|
+
bitcoin/tx_listener/zmq_listener.py,sha256=HCLtR6s6kBH1IP8WXJA9UfnR8kczwk7Mdtexn3r1yoI,1215
|
21
21
|
bitcoin/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
-
bitcoin/utils/benchmark.py,sha256=
|
23
|
-
bitcoin/utils/bitcoin_rpc.py,sha256=
|
22
|
+
bitcoin/utils/benchmark.py,sha256=HXZy9vU34MKrBoVjx3y982TFEOuSmOz5HWf5LcFwaZA,817
|
23
|
+
bitcoin/utils/bitcoin_rpc.py,sha256=wyTIahgdSyiR1ryPi75n33ERuKQK630YTO9hNs_MML4,1358
|
24
24
|
bitcoin/utils/bitcoin_utils.py,sha256=mrnRPqUa2U2EMKu7rrPV_bW1sL2CJUfbAom0Zdamydk,631
|
25
25
|
bitcoin/utils/constants.py,sha256=irZLlArgica2VckyckEYxH5D5KjvdF52dtBMWswqw8k,52
|
26
|
+
bitcoin/utils/context_aware_logging.py,sha256=qPBhi44Hks_LaiW516T9oGqY1WIbUQqHOR1KckG5l1s,1307
|
26
27
|
bitcoin/utils/inscription_utils.py,sha256=8QbOJ1o1n1bMFsPREGLzwFjnGzfuARgJCPr6ORhP44o,193
|
27
|
-
bitcoinwatcher-2.
|
28
|
-
bitcoinwatcher-2.
|
29
|
-
bitcoinwatcher-2.
|
30
|
-
bitcoinwatcher-2.
|
31
|
-
bitcoinwatcher-2.
|
28
|
+
bitcoinwatcher-2.6.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
29
|
+
bitcoinwatcher-2.6.dist-info/METADATA,sha256=Oyi_L_ZKMkkhUdbDAFOcES7u6qB5Mcjbnz02iIv7FcU,14989
|
30
|
+
bitcoinwatcher-2.6.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
31
|
+
bitcoinwatcher-2.6.dist-info/top_level.txt,sha256=YdUgzLdCiMlrwaKyDqHA1acEd23QFko5bv7D6nBANJ0,8
|
32
|
+
bitcoinwatcher-2.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|