hive-nectar 0.2.9__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.
- hive_nectar-0.2.9.dist-info/METADATA +194 -0
- hive_nectar-0.2.9.dist-info/RECORD +87 -0
- hive_nectar-0.2.9.dist-info/WHEEL +4 -0
- hive_nectar-0.2.9.dist-info/entry_points.txt +2 -0
- hive_nectar-0.2.9.dist-info/licenses/LICENSE.txt +23 -0
- nectar/__init__.py +37 -0
- nectar/account.py +5076 -0
- nectar/amount.py +553 -0
- nectar/asciichart.py +303 -0
- nectar/asset.py +122 -0
- nectar/block.py +574 -0
- nectar/blockchain.py +1242 -0
- nectar/blockchaininstance.py +2590 -0
- nectar/blockchainobject.py +263 -0
- nectar/cli.py +5937 -0
- nectar/comment.py +1552 -0
- nectar/community.py +854 -0
- nectar/constants.py +95 -0
- nectar/discussions.py +1437 -0
- nectar/exceptions.py +152 -0
- nectar/haf.py +381 -0
- nectar/hive.py +630 -0
- nectar/imageuploader.py +114 -0
- nectar/instance.py +113 -0
- nectar/market.py +876 -0
- nectar/memo.py +542 -0
- nectar/message.py +379 -0
- nectar/nodelist.py +309 -0
- nectar/price.py +603 -0
- nectar/profile.py +74 -0
- nectar/py.typed +0 -0
- nectar/rc.py +333 -0
- nectar/snapshot.py +1024 -0
- nectar/storage.py +62 -0
- nectar/transactionbuilder.py +659 -0
- nectar/utils.py +630 -0
- nectar/version.py +3 -0
- nectar/vote.py +722 -0
- nectar/wallet.py +472 -0
- nectar/witness.py +728 -0
- nectarapi/__init__.py +12 -0
- nectarapi/exceptions.py +126 -0
- nectarapi/graphenerpc.py +596 -0
- nectarapi/node.py +194 -0
- nectarapi/noderpc.py +79 -0
- nectarapi/openapi.py +107 -0
- nectarapi/py.typed +0 -0
- nectarapi/rpcutils.py +98 -0
- nectarapi/version.py +3 -0
- nectarbase/__init__.py +15 -0
- nectarbase/ledgertransactions.py +106 -0
- nectarbase/memo.py +242 -0
- nectarbase/objects.py +521 -0
- nectarbase/objecttypes.py +21 -0
- nectarbase/operationids.py +102 -0
- nectarbase/operations.py +1357 -0
- nectarbase/py.typed +0 -0
- nectarbase/signedtransactions.py +89 -0
- nectarbase/transactions.py +11 -0
- nectarbase/version.py +3 -0
- nectargraphenebase/__init__.py +27 -0
- nectargraphenebase/account.py +1121 -0
- nectargraphenebase/aes.py +49 -0
- nectargraphenebase/base58.py +197 -0
- nectargraphenebase/bip32.py +575 -0
- nectargraphenebase/bip38.py +110 -0
- nectargraphenebase/chains.py +15 -0
- nectargraphenebase/dictionary.py +2 -0
- nectargraphenebase/ecdsasig.py +309 -0
- nectargraphenebase/objects.py +130 -0
- nectargraphenebase/objecttypes.py +8 -0
- nectargraphenebase/operationids.py +5 -0
- nectargraphenebase/operations.py +25 -0
- nectargraphenebase/prefix.py +13 -0
- nectargraphenebase/py.typed +0 -0
- nectargraphenebase/signedtransactions.py +221 -0
- nectargraphenebase/types.py +557 -0
- nectargraphenebase/unsignedtransactions.py +288 -0
- nectargraphenebase/version.py +3 -0
- nectarstorage/__init__.py +57 -0
- nectarstorage/base.py +317 -0
- nectarstorage/exceptions.py +15 -0
- nectarstorage/interfaces.py +244 -0
- nectarstorage/masterpassword.py +237 -0
- nectarstorage/py.typed +0 -0
- nectarstorage/ram.py +27 -0
- nectarstorage/sqlite.py +343 -0
nectar/block.py
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import date, datetime
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from nectar.instance import shared_blockchain_instance
|
|
8
|
+
from nectar.utils import formatTimeString, parse_time
|
|
9
|
+
|
|
10
|
+
from .blockchainobject import BlockchainObject
|
|
11
|
+
from .exceptions import BlockDoesNotExistsException
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Block(BlockchainObject):
|
|
15
|
+
"""Read a single block from the chain
|
|
16
|
+
|
|
17
|
+
:param int block: block number
|
|
18
|
+
:param Hive blockchain_instance: Hive
|
|
19
|
+
instance
|
|
20
|
+
:param bool lazy: Use lazy loading
|
|
21
|
+
:param bool only_ops: Includes only operations, when set to True (default: False)
|
|
22
|
+
:param bool only_virtual_ops: Includes only virtual operations (default: False)
|
|
23
|
+
|
|
24
|
+
Instances of this class are dictionaries that come with additional
|
|
25
|
+
methods (see below) that allow dealing with a block and its
|
|
26
|
+
corresponding functions.
|
|
27
|
+
|
|
28
|
+
When only_virtual_ops is set to True, only_ops is always set to True.
|
|
29
|
+
|
|
30
|
+
In addition to the block data, the block number is stored as self["id"] or self.identifier.
|
|
31
|
+
|
|
32
|
+
.. code-block:: python
|
|
33
|
+
|
|
34
|
+
>>> from nectar.block import Block
|
|
35
|
+
>>> block = Block(1)
|
|
36
|
+
>>> print(block)
|
|
37
|
+
<Block 1>
|
|
38
|
+
|
|
39
|
+
.. note:: This class comes with its own caching function to reduce the
|
|
40
|
+
load on the API server. Instances of this class can be
|
|
41
|
+
refreshed with ``Account.refresh()``.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
block: int | float | dict,
|
|
48
|
+
only_ops: bool = False,
|
|
49
|
+
only_virtual_ops: bool = False,
|
|
50
|
+
full: bool = True,
|
|
51
|
+
lazy: bool = False,
|
|
52
|
+
blockchain_instance: Any = None,
|
|
53
|
+
**kwargs,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Initialize a Block object representing a single blockchain block.
|
|
57
|
+
|
|
58
|
+
block may be an integer (block number), a float (will be converted to int), or a dict containing block data (which will be parsed). Controls:
|
|
59
|
+
- only_ops: load only operations from the block.
|
|
60
|
+
- only_virtual_ops: load only virtual operations.
|
|
61
|
+
- full: if True, populate full block data; if False, keep a minimal representation.
|
|
62
|
+
- lazy: if True, defer fetching full data until needed.
|
|
63
|
+
|
|
64
|
+
If no identifier is present after initialization, the block's identifier is set to its numeric block number.
|
|
65
|
+
"""
|
|
66
|
+
self.full = full
|
|
67
|
+
self.lazy = lazy
|
|
68
|
+
self.only_ops = only_ops
|
|
69
|
+
self.only_virtual_ops = only_virtual_ops
|
|
70
|
+
if isinstance(block, float):
|
|
71
|
+
block = int(block)
|
|
72
|
+
elif isinstance(block, dict):
|
|
73
|
+
block = self._parse_json_data(block)
|
|
74
|
+
super().__init__(
|
|
75
|
+
block,
|
|
76
|
+
lazy=lazy,
|
|
77
|
+
full=full,
|
|
78
|
+
blockchain_instance=blockchain_instance,
|
|
79
|
+
**kwargs,
|
|
80
|
+
)
|
|
81
|
+
if self.identifier is None:
|
|
82
|
+
self.identifier = self.get(self.id_item)
|
|
83
|
+
|
|
84
|
+
def _parse_json_data(self, block: dict) -> dict:
|
|
85
|
+
parse_times = [
|
|
86
|
+
"timestamp",
|
|
87
|
+
]
|
|
88
|
+
for p in parse_times:
|
|
89
|
+
if p in block and isinstance(block.get(p), str):
|
|
90
|
+
block[p] = parse_time(block.get(p, "1970-01-01T00:00:00"))
|
|
91
|
+
if "transactions" in block:
|
|
92
|
+
for i in range(len(block["transactions"])):
|
|
93
|
+
if "expiration" in block["transactions"][i] and isinstance(
|
|
94
|
+
block["transactions"][i]["expiration"], str
|
|
95
|
+
):
|
|
96
|
+
block["transactions"][i]["expiration"] = parse_time(
|
|
97
|
+
block["transactions"][i]["expiration"]
|
|
98
|
+
)
|
|
99
|
+
elif "operations" in block:
|
|
100
|
+
for i in range(len(block["operations"])):
|
|
101
|
+
if "timestamp" in block["operations"][i] and isinstance(
|
|
102
|
+
block["operations"][i]["timestamp"], str
|
|
103
|
+
):
|
|
104
|
+
block["operations"][i]["timestamp"] = parse_time(
|
|
105
|
+
block["operations"][i]["timestamp"]
|
|
106
|
+
)
|
|
107
|
+
return block
|
|
108
|
+
|
|
109
|
+
def json(self) -> dict:
|
|
110
|
+
output = self.copy()
|
|
111
|
+
parse_times = [
|
|
112
|
+
"timestamp",
|
|
113
|
+
]
|
|
114
|
+
for p in parse_times:
|
|
115
|
+
if p in output:
|
|
116
|
+
p_date = output.get(p, datetime(1970, 1, 1, 0, 0))
|
|
117
|
+
if isinstance(p_date, (datetime, date)):
|
|
118
|
+
output[p] = formatTimeString(p_date)
|
|
119
|
+
else:
|
|
120
|
+
output[p] = p_date
|
|
121
|
+
|
|
122
|
+
if "transactions" in output:
|
|
123
|
+
for i in range(len(output["transactions"])):
|
|
124
|
+
if "expiration" in output["transactions"][i] and isinstance(
|
|
125
|
+
output["transactions"][i]["expiration"], (datetime, date)
|
|
126
|
+
):
|
|
127
|
+
output["transactions"][i]["expiration"] = formatTimeString(
|
|
128
|
+
output["transactions"][i]["expiration"]
|
|
129
|
+
)
|
|
130
|
+
elif "operations" in output:
|
|
131
|
+
for i in range(len(output["operations"])):
|
|
132
|
+
if "timestamp" in output["operations"][i] and isinstance(
|
|
133
|
+
output["operations"][i]["timestamp"], (datetime, date)
|
|
134
|
+
):
|
|
135
|
+
output["operations"][i]["timestamp"] = formatTimeString(
|
|
136
|
+
output["operations"][i]["timestamp"]
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
ret = json.loads(str(json.dumps(output)))
|
|
140
|
+
output = self._parse_json_data(output)
|
|
141
|
+
return ret
|
|
142
|
+
|
|
143
|
+
def refresh(self) -> None:
|
|
144
|
+
"""Even though blocks never change, you freshly obtain its contents
|
|
145
|
+
from an API with this method
|
|
146
|
+
"""
|
|
147
|
+
if self.identifier is None:
|
|
148
|
+
return
|
|
149
|
+
if not self.blockchain.is_connected():
|
|
150
|
+
return
|
|
151
|
+
self.blockchain.rpc.set_next_node_on_empty_reply(False)
|
|
152
|
+
if self.only_ops or self.only_virtual_ops:
|
|
153
|
+
ops_ops = self.blockchain.rpc.get_ops_in_block(
|
|
154
|
+
{"block_num": self.identifier, "only_virtual": self.only_virtual_ops}
|
|
155
|
+
)
|
|
156
|
+
ops = ops_ops["ops"] if ops_ops is not None else []
|
|
157
|
+
timestamp = ops[0]["timestamp"] if ops else "1970-01-01T00:00:00"
|
|
158
|
+
block = {
|
|
159
|
+
"block": self.identifier,
|
|
160
|
+
"timestamp": timestamp,
|
|
161
|
+
"operations": ops,
|
|
162
|
+
}
|
|
163
|
+
else:
|
|
164
|
+
block = self.blockchain.rpc.get_block({"block_num": self.identifier})
|
|
165
|
+
if block and "block" in block:
|
|
166
|
+
block = block["block"]
|
|
167
|
+
if not block:
|
|
168
|
+
message = f"Block {self.identifier} does not exist or is not available from {self.blockchain.rpc.url}"
|
|
169
|
+
raise BlockDoesNotExistsException(message)
|
|
170
|
+
block = self._parse_json_data(block)
|
|
171
|
+
super().__init__(block, lazy=self.lazy, full=self.full, blockchain_instance=self.blockchain)
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def block_num(self) -> int | None:
|
|
175
|
+
"""Returns the block number"""
|
|
176
|
+
if "block_id" in self:
|
|
177
|
+
return int(self["block_id"][:8], base=16)
|
|
178
|
+
elif "block" in self:
|
|
179
|
+
return int(self["block"])
|
|
180
|
+
else:
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def time(self) -> datetime:
|
|
184
|
+
"""Return a datetime instance for the timestamp of this block"""
|
|
185
|
+
return self["timestamp"]
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def transactions(self) -> list[dict[str, Any]]:
|
|
189
|
+
"""Returns all transactions as list"""
|
|
190
|
+
if self.only_ops or self.only_virtual_ops:
|
|
191
|
+
return list()
|
|
192
|
+
trxs = []
|
|
193
|
+
if "transactions" not in self:
|
|
194
|
+
return []
|
|
195
|
+
trx_id = 0
|
|
196
|
+
for trx in self["transactions"]:
|
|
197
|
+
trx_new = {"transaction_id": self["transaction_ids"][trx_id]}
|
|
198
|
+
trx_new.update(trx.copy())
|
|
199
|
+
trx_new.update({"block_num": self.block_num, "transaction_num": trx_id})
|
|
200
|
+
trxs.append(trx_new)
|
|
201
|
+
trx_id += 1
|
|
202
|
+
return trxs
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def operations(self) -> list[Any]:
|
|
206
|
+
"""Returns all block operations as list"""
|
|
207
|
+
|
|
208
|
+
def _normalize_op_type(op_type: Any) -> Any:
|
|
209
|
+
if isinstance(op_type, int):
|
|
210
|
+
try:
|
|
211
|
+
from nectarbase.operationids import getOperationNameForId
|
|
212
|
+
|
|
213
|
+
return getOperationNameForId(op_type)
|
|
214
|
+
except Exception:
|
|
215
|
+
return op_type
|
|
216
|
+
return op_type
|
|
217
|
+
|
|
218
|
+
if (self.only_ops or self.only_virtual_ops) and "operations" in self:
|
|
219
|
+
ops = self["operations"]
|
|
220
|
+
# Normalize get_ops_in_block format (which wraps op in "op" key)
|
|
221
|
+
normalized_ops = []
|
|
222
|
+
for x in ops:
|
|
223
|
+
if isinstance(x, dict) and "op" in x:
|
|
224
|
+
# Wrapper found: get_ops_in_block format
|
|
225
|
+
raw_op = x["op"]
|
|
226
|
+
if isinstance(raw_op, list):
|
|
227
|
+
op_dict = {
|
|
228
|
+
"type": _normalize_op_type(raw_op[0]),
|
|
229
|
+
"value": raw_op[1],
|
|
230
|
+
}
|
|
231
|
+
elif isinstance(raw_op, dict):
|
|
232
|
+
op_dict = raw_op.copy()
|
|
233
|
+
if "type" in op_dict:
|
|
234
|
+
op_dict["type"] = _normalize_op_type(op_dict["type"])
|
|
235
|
+
else:
|
|
236
|
+
continue # Should not happen
|
|
237
|
+
|
|
238
|
+
# Inject metadata from wrapper
|
|
239
|
+
if "trx_id" in x:
|
|
240
|
+
op_dict["transaction_id"] = x["trx_id"]
|
|
241
|
+
if "timestamp" in x:
|
|
242
|
+
op_dict["timestamp"] = x["timestamp"]
|
|
243
|
+
if "block" in x:
|
|
244
|
+
op_dict["block_num"] = x["block"]
|
|
245
|
+
|
|
246
|
+
normalized_ops.append(op_dict)
|
|
247
|
+
elif isinstance(x, (list, tuple)) and len(x) >= 2:
|
|
248
|
+
normalized_ops.append({"type": _normalize_op_type(x[0]), "value": x[1]})
|
|
249
|
+
elif isinstance(x, dict) and "type" in x and "value" in x:
|
|
250
|
+
op_dict = x.copy()
|
|
251
|
+
op_dict["type"] = _normalize_op_type(op_dict["type"])
|
|
252
|
+
normalized_ops.append(op_dict)
|
|
253
|
+
else:
|
|
254
|
+
# Legacy or direct format?
|
|
255
|
+
normalized_ops.append(x)
|
|
256
|
+
return normalized_ops
|
|
257
|
+
ops = []
|
|
258
|
+
trxs = []
|
|
259
|
+
if "transactions" in self:
|
|
260
|
+
trxs = self["transactions"]
|
|
261
|
+
|
|
262
|
+
for tx_idx, tx in enumerate(trxs):
|
|
263
|
+
if "operations" not in tx:
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
# Get transaction_id if available (it is usually in a separate list in the block)
|
|
267
|
+
current_trx_id = None
|
|
268
|
+
if "transaction_ids" in self and len(self["transaction_ids"]) > tx_idx:
|
|
269
|
+
current_trx_id = self["transaction_ids"][tx_idx]
|
|
270
|
+
# Provide fallback if it was somehow already in tx (e.g. from different API)
|
|
271
|
+
elif "transaction_id" in tx:
|
|
272
|
+
current_trx_id = tx["transaction_id"]
|
|
273
|
+
|
|
274
|
+
for op in tx["operations"]:
|
|
275
|
+
# Replace op id by op name when numeric
|
|
276
|
+
if isinstance(op, (list, tuple)) and len(op) > 0:
|
|
277
|
+
op[0] = _normalize_op_type(op[0])
|
|
278
|
+
if isinstance(op, list):
|
|
279
|
+
if self.only_ops or self.only_virtual_ops:
|
|
280
|
+
op_dict = {"type": op[0], "value": op[1]}
|
|
281
|
+
# Inject formatting that helpers.py expects
|
|
282
|
+
if current_trx_id:
|
|
283
|
+
op_dict["transaction_id"] = current_trx_id
|
|
284
|
+
|
|
285
|
+
if "block_num" in tx:
|
|
286
|
+
op_dict["block_num"] = tx["block_num"]
|
|
287
|
+
elif self.block_num:
|
|
288
|
+
op_dict["block_num"] = self.block_num
|
|
289
|
+
|
|
290
|
+
op_dict["timestamp"] = self.time()
|
|
291
|
+
ops.append(op_dict)
|
|
292
|
+
else:
|
|
293
|
+
ops.append(list(op))
|
|
294
|
+
elif isinstance(op, dict):
|
|
295
|
+
# If op is already a dictionary, we still need to inject metadata if we are in only_ops mode
|
|
296
|
+
if self.only_ops or self.only_virtual_ops:
|
|
297
|
+
op_dict = op.copy()
|
|
298
|
+
if "type" in op_dict:
|
|
299
|
+
op_dict["type"] = _normalize_op_type(op_dict["type"])
|
|
300
|
+
|
|
301
|
+
# In some cases op might be {"op": {"type": ..., "value": ...}} ??
|
|
302
|
+
# But loop above handles raw operations list.
|
|
303
|
+
# Assuming op is {"type": ..., "value": ...} or similar structure
|
|
304
|
+
|
|
305
|
+
if current_trx_id and "transaction_id" not in op_dict:
|
|
306
|
+
op_dict["transaction_id"] = current_trx_id
|
|
307
|
+
|
|
308
|
+
if "block_num" not in op_dict:
|
|
309
|
+
if "block_num" in tx:
|
|
310
|
+
op_dict["block_num"] = tx["block_num"]
|
|
311
|
+
elif self.block_num:
|
|
312
|
+
op_dict["block_num"] = self.block_num
|
|
313
|
+
|
|
314
|
+
if "timestamp" not in op_dict:
|
|
315
|
+
op_dict["timestamp"] = self.time()
|
|
316
|
+
ops.append(op_dict)
|
|
317
|
+
else:
|
|
318
|
+
ops.append(op.copy())
|
|
319
|
+
else:
|
|
320
|
+
ops.append(op.copy())
|
|
321
|
+
return ops
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def json_transactions(self) -> list[dict[str, Any]]:
|
|
325
|
+
"""Returns all transactions as list, all dates are strings."""
|
|
326
|
+
if self.only_ops or self.only_virtual_ops:
|
|
327
|
+
return list()
|
|
328
|
+
trxs = []
|
|
329
|
+
if "transactions" not in self:
|
|
330
|
+
return []
|
|
331
|
+
trx_id = 0
|
|
332
|
+
for trx in self["transactions"]:
|
|
333
|
+
trx_new = {"transaction_id": self["transaction_ids"][trx_id]}
|
|
334
|
+
trx_new.update(trx.copy())
|
|
335
|
+
trx_new.update({"block_num": self.block_num, "transaction_num": trx_id})
|
|
336
|
+
if "expiration" in trx:
|
|
337
|
+
p_date = trx.get("expiration", datetime(1970, 1, 1, 0, 0))
|
|
338
|
+
if isinstance(p_date, (datetime, date)):
|
|
339
|
+
trx_new.update({"expiration": formatTimeString(p_date)})
|
|
340
|
+
|
|
341
|
+
trxs.append(trx_new)
|
|
342
|
+
trx_id += 1
|
|
343
|
+
return trxs
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def json_operations(self) -> list[Any]:
|
|
347
|
+
"""Returns all block operations as list, all dates are strings."""
|
|
348
|
+
if self.only_ops or self.only_virtual_ops:
|
|
349
|
+
return self["operations"]
|
|
350
|
+
ops = []
|
|
351
|
+
for tx in self["transactions"]:
|
|
352
|
+
for op in tx["operations"]:
|
|
353
|
+
if "operations" not in tx:
|
|
354
|
+
continue
|
|
355
|
+
# Replace op id by op name when numeric
|
|
356
|
+
if isinstance(op, (list, tuple)) and len(op) > 0 and isinstance(op[0], int):
|
|
357
|
+
try:
|
|
358
|
+
from nectarbase.operationids import getOperationNameForId
|
|
359
|
+
|
|
360
|
+
op[0] = getOperationNameForId(op[0])
|
|
361
|
+
except Exception:
|
|
362
|
+
pass
|
|
363
|
+
if isinstance(op, list):
|
|
364
|
+
op_new = list(op)
|
|
365
|
+
else:
|
|
366
|
+
op_new = op.copy()
|
|
367
|
+
if "timestamp" in op:
|
|
368
|
+
p_date = op.get("timestamp", datetime(1970, 1, 1, 0, 0))
|
|
369
|
+
if isinstance(p_date, (datetime, date)):
|
|
370
|
+
if isinstance(op_new, dict):
|
|
371
|
+
op_new.update({"timestamp": formatTimeString(p_date)})
|
|
372
|
+
else:
|
|
373
|
+
# Handle list case - find timestamp in list and update it
|
|
374
|
+
for i, item in enumerate(op_new):
|
|
375
|
+
if isinstance(item, dict) and "timestamp" in item:
|
|
376
|
+
op_new[i] = {
|
|
377
|
+
**item,
|
|
378
|
+
"timestamp": formatTimeString(p_date),
|
|
379
|
+
}
|
|
380
|
+
break
|
|
381
|
+
ops.append(op_new)
|
|
382
|
+
return ops
|
|
383
|
+
|
|
384
|
+
def ops_statistics(self, add_to_ops_stat: dict[str, int] | None = None) -> dict[str, int]:
|
|
385
|
+
"""Returns a statistic with the occurrence of the different operation types"""
|
|
386
|
+
if add_to_ops_stat is None:
|
|
387
|
+
import nectarbase.operationids
|
|
388
|
+
|
|
389
|
+
ops_stat = nectarbase.operationids.operations.copy()
|
|
390
|
+
for key in ops_stat:
|
|
391
|
+
if isinstance(key, int):
|
|
392
|
+
ops_stat[key] = 0
|
|
393
|
+
else:
|
|
394
|
+
ops_stat = add_to_ops_stat.copy()
|
|
395
|
+
for op in self.operations:
|
|
396
|
+
if "op" in op:
|
|
397
|
+
op = op["op"]
|
|
398
|
+
if isinstance(op, dict) and "type" in op:
|
|
399
|
+
op_type = op["type"]
|
|
400
|
+
if len(op_type) > 10 and op_type[len(op_type) - 10 :] == "_operation":
|
|
401
|
+
op_type = op_type[:-10]
|
|
402
|
+
else:
|
|
403
|
+
op_type = op[0]
|
|
404
|
+
if op_type in ops_stat:
|
|
405
|
+
ops_stat[op_type] += 1
|
|
406
|
+
return ops_stat
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class BlockHeader(BlockchainObject):
|
|
410
|
+
"""Read a single block header from the chain
|
|
411
|
+
|
|
412
|
+
:param int block: block number
|
|
413
|
+
:param Hive blockchain_instance: Hive
|
|
414
|
+
instance
|
|
415
|
+
:param bool lazy: Use lazy loading
|
|
416
|
+
|
|
417
|
+
In addition to the block data, the block number is stored as self["id"] or self.identifier.
|
|
418
|
+
|
|
419
|
+
.. code-block:: python
|
|
420
|
+
|
|
421
|
+
>>> from nectar.block import BlockHeader
|
|
422
|
+
>>> block = BlockHeader(1)
|
|
423
|
+
>>> print(block)
|
|
424
|
+
<BlockHeader 1>
|
|
425
|
+
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
def __init__(
|
|
429
|
+
self,
|
|
430
|
+
block: int | float | dict,
|
|
431
|
+
full: bool = True,
|
|
432
|
+
lazy: bool = False,
|
|
433
|
+
blockchain_instance: Any = None,
|
|
434
|
+
**kwargs,
|
|
435
|
+
) -> None:
|
|
436
|
+
"""
|
|
437
|
+
Initialize a BlockHeader.
|
|
438
|
+
|
|
439
|
+
One-line summary:
|
|
440
|
+
Create a BlockHeader wrapper for a block header, optionally in lazy or full mode.
|
|
441
|
+
|
|
442
|
+
Parameters:
|
|
443
|
+
block (int | float | dict): Block number (floats are converted to int) or a header dict.
|
|
444
|
+
full (bool): If True, populate the object with full header data; otherwise keep a minimal representation.
|
|
445
|
+
lazy (bool): If True, delay API fetching until data is accessed.
|
|
446
|
+
|
|
447
|
+
Notes:
|
|
448
|
+
If no blockchain_instance is provided, the module's shared blockchain instance is used.
|
|
449
|
+
"""
|
|
450
|
+
self.full = full
|
|
451
|
+
self.lazy = lazy
|
|
452
|
+
if isinstance(block, float):
|
|
453
|
+
block = int(block)
|
|
454
|
+
super().__init__(
|
|
455
|
+
block,
|
|
456
|
+
lazy=lazy,
|
|
457
|
+
full=full,
|
|
458
|
+
blockchain_instance=blockchain_instance,
|
|
459
|
+
**kwargs,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
def refresh(self) -> None:
|
|
463
|
+
"""Even though blocks never change, you freshly obtain its contents
|
|
464
|
+
from an API with this method
|
|
465
|
+
"""
|
|
466
|
+
if not self.blockchain.is_connected():
|
|
467
|
+
return None
|
|
468
|
+
self.blockchain.rpc.set_next_node_on_empty_reply(False)
|
|
469
|
+
block = self.blockchain.rpc.get_block_header({"block_num": self.identifier})
|
|
470
|
+
if block is not None and "header" in block:
|
|
471
|
+
block = block["header"]
|
|
472
|
+
if not block:
|
|
473
|
+
raise BlockDoesNotExistsException(str(self.identifier))
|
|
474
|
+
block = self._parse_json_data(block)
|
|
475
|
+
super().__init__(block, lazy=self.lazy, full=self.full, blockchain_instance=self.blockchain)
|
|
476
|
+
|
|
477
|
+
def time(self) -> datetime:
|
|
478
|
+
"""Return a datetime instance for the timestamp of this block"""
|
|
479
|
+
return self["timestamp"]
|
|
480
|
+
|
|
481
|
+
@property
|
|
482
|
+
def block_num(self) -> int:
|
|
483
|
+
"""Returns the block number"""
|
|
484
|
+
if self.identifier is None:
|
|
485
|
+
raise ValueError("Block identifier is not set")
|
|
486
|
+
if isinstance(self.identifier, int):
|
|
487
|
+
return self.identifier
|
|
488
|
+
# Try to convert string or other types to int
|
|
489
|
+
return int(self.identifier)
|
|
490
|
+
|
|
491
|
+
def _parse_json_data(self, block: dict) -> dict:
|
|
492
|
+
parse_times = [
|
|
493
|
+
"timestamp",
|
|
494
|
+
]
|
|
495
|
+
for p in parse_times:
|
|
496
|
+
if p in block and isinstance(block.get(p), str):
|
|
497
|
+
block[p] = parse_time(block.get(p, "1970-01-01T00:00:00"))
|
|
498
|
+
return block
|
|
499
|
+
|
|
500
|
+
def json(self) -> dict:
|
|
501
|
+
output = self.copy()
|
|
502
|
+
parse_times = [
|
|
503
|
+
"timestamp",
|
|
504
|
+
]
|
|
505
|
+
for p in parse_times:
|
|
506
|
+
if p in output:
|
|
507
|
+
p_date = output.get(p, datetime(1970, 1, 1, 0, 0))
|
|
508
|
+
if isinstance(p_date, (datetime, date)):
|
|
509
|
+
output[p] = formatTimeString(p_date)
|
|
510
|
+
else:
|
|
511
|
+
output[p] = p_date
|
|
512
|
+
return json.loads(str(json.dumps(output)))
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
class Blocks(list):
|
|
516
|
+
"""Obtain a list of blocks
|
|
517
|
+
|
|
518
|
+
:param list name_list: list of accounts to fetch
|
|
519
|
+
:param int count: (optional) maximum number of accounts
|
|
520
|
+
to fetch per call, defaults to 100
|
|
521
|
+
:param Hive blockchain_instance: Hive() instance to use when
|
|
522
|
+
accessing RPC
|
|
523
|
+
"""
|
|
524
|
+
|
|
525
|
+
def __init__(
|
|
526
|
+
self,
|
|
527
|
+
starting_block_num: int,
|
|
528
|
+
count: int = 1000,
|
|
529
|
+
lazy: bool = False,
|
|
530
|
+
full: bool = True,
|
|
531
|
+
only_ops: bool = False,
|
|
532
|
+
only_virtual_ops: bool = False,
|
|
533
|
+
blockchain_instance: Any = None,
|
|
534
|
+
**kwargs,
|
|
535
|
+
) -> None:
|
|
536
|
+
"""
|
|
537
|
+
Initialize a Blocks collection by fetching a contiguous range of blocks from the chain and populating the list with Block objects.
|
|
538
|
+
|
|
539
|
+
If a blockchain_instance is provided it is used; otherwise the shared blockchain instance is used. If the chosen instance is not connected, the initializer returns early and the Blocks object remains empty.
|
|
540
|
+
|
|
541
|
+
Parameters:
|
|
542
|
+
starting_block_num (int): First block number to retrieve.
|
|
543
|
+
count (int, optional): Number of consecutive blocks to fetch. Defaults to 1000.
|
|
544
|
+
lazy (bool, optional): If True, create Block objects in lazy mode (defer full parsing). Defaults to False.
|
|
545
|
+
full (bool, optional): If True, create Block objects with full data loaded (subject to lazy). Defaults to True.
|
|
546
|
+
only_ops (bool, optional): If True, blocks will contain only regular operations (no block metadata). Defaults to False.
|
|
547
|
+
only_virtual_ops (bool, optional): If True, blocks will contain only virtual operations. Defaults to False.
|
|
548
|
+
"""
|
|
549
|
+
self.blockchain = blockchain_instance or shared_blockchain_instance()
|
|
550
|
+
|
|
551
|
+
if not self.blockchain.is_connected():
|
|
552
|
+
return
|
|
553
|
+
blocks = []
|
|
554
|
+
|
|
555
|
+
self.blockchain.rpc.set_next_node_on_empty_reply(False)
|
|
556
|
+
|
|
557
|
+
blocks = self.blockchain.rpc.get_block_range(
|
|
558
|
+
{"starting_block_num": starting_block_num, "count": count}
|
|
559
|
+
)["blocks"]
|
|
560
|
+
|
|
561
|
+
super().__init__(
|
|
562
|
+
[
|
|
563
|
+
Block(
|
|
564
|
+
x,
|
|
565
|
+
lazy=lazy,
|
|
566
|
+
full=full,
|
|
567
|
+
only_ops=only_ops,
|
|
568
|
+
only_virtual_ops=only_virtual_ops,
|
|
569
|
+
blockchain_instance=self.blockchain,
|
|
570
|
+
**kwargs,
|
|
571
|
+
)
|
|
572
|
+
for x in blocks
|
|
573
|
+
]
|
|
574
|
+
)
|